vmik-fluent-plugin-google-cloud 0.5.5.alpha1
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.
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.rdoc +46 -0
- data/Rakefile +42 -0
- data/fluent-plugin-google-cloud.gemspec +34 -0
- data/lib/fluent/plugin/out_google_cloud.rb +1184 -0
- data/lib/google/logging/type/http_request_pb.rb +30 -0
- data/lib/google/logging/type/log_severity_pb.rb +26 -0
- data/lib/google/logging/v1/log_entry_pb.rb +52 -0
- data/lib/google/logging/v1/logging_pb.rb +84 -0
- data/lib/google/logging/v1/logging_services_pb.rb +150 -0
- data/test/helper.rb +40 -0
- data/test/plugin/base_test.rb +1534 -0
- data/test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12 +0 -0
- data/test/plugin/data/credentials.json +7 -0
- data/test/plugin/data/iam-credentials.json +11 -0
- data/test/plugin/data/invalid_credentials.json +8 -0
- data/test/plugin/test_out_google_cloud.rb +297 -0
- data/test/plugin/test_out_google_cloud_grpc.rb +381 -0
- data/vmik-fluent-plugin-google-cloud-0.5.5.gem +0 -0
- metadata +291 -0
@@ -0,0 +1,1534 @@
|
|
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
|
+
REQUIRE_VALID_TAGS_CONFIG = %(
|
132
|
+
require_valid_tags true
|
133
|
+
)
|
134
|
+
|
135
|
+
NO_METADATA_SERVICE_CONFIG = %(
|
136
|
+
use_metadata_service false
|
137
|
+
)
|
138
|
+
|
139
|
+
NO_DETECT_SUBSERVICE_CONFIG = %(
|
140
|
+
detect_subservice false
|
141
|
+
)
|
142
|
+
|
143
|
+
CUSTOM_METADATA_CONFIG = %(
|
144
|
+
project_id #{CUSTOM_PROJECT_ID}
|
145
|
+
zone #{CUSTOM_ZONE}
|
146
|
+
vm_id #{CUSTOM_VM_ID}
|
147
|
+
vm_name #{CUSTOM_HOSTNAME}
|
148
|
+
)
|
149
|
+
|
150
|
+
CONFIG_MISSING_METADATA_PROJECT_ID = %(
|
151
|
+
zone #{CUSTOM_ZONE}
|
152
|
+
vm_id #{CUSTOM_VM_ID}
|
153
|
+
)
|
154
|
+
CONFIG_MISSING_METADATA_ZONE = %(
|
155
|
+
project_id #{CUSTOM_PROJECT_ID}
|
156
|
+
vm_id #{CUSTOM_VM_ID}
|
157
|
+
)
|
158
|
+
CONFIG_MISSING_METADATA_VM_ID = %(
|
159
|
+
project_id #{CUSTOM_PROJECT_ID}
|
160
|
+
zone #{CUSTOM_ZONE}
|
161
|
+
)
|
162
|
+
CONFIG_MISSING_METADATA_ALL = %(
|
163
|
+
)
|
164
|
+
|
165
|
+
CONFIG_EC2_PROJECT_ID = %(
|
166
|
+
project_id #{EC2_PROJECT_ID}
|
167
|
+
)
|
168
|
+
|
169
|
+
CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID = %(
|
170
|
+
project_id #{EC2_PROJECT_ID}
|
171
|
+
vm_id #{CUSTOM_VM_ID}
|
172
|
+
)
|
173
|
+
|
174
|
+
# Service configurations for various services
|
175
|
+
COMPUTE_SERVICE_NAME = 'compute.googleapis.com'
|
176
|
+
APPENGINE_SERVICE_NAME = 'appengine.googleapis.com'
|
177
|
+
CONTAINER_SERVICE_NAME = 'container.googleapis.com'
|
178
|
+
CLOUDFUNCTIONS_SERVICE_NAME = 'cloudfunctions.googleapis.com'
|
179
|
+
EC2_SERVICE_NAME = 'ec2.amazonaws.com'
|
180
|
+
|
181
|
+
COMPUTE_PARAMS = {
|
182
|
+
service_name: COMPUTE_SERVICE_NAME,
|
183
|
+
log_name: 'test',
|
184
|
+
project_id: PROJECT_ID,
|
185
|
+
zone: ZONE,
|
186
|
+
labels: {
|
187
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
188
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
189
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
VMENGINE_PARAMS = {
|
194
|
+
service_name: APPENGINE_SERVICE_NAME,
|
195
|
+
log_name: "#{APPENGINE_SERVICE_NAME}%2Ftest",
|
196
|
+
project_id: PROJECT_ID,
|
197
|
+
zone: ZONE,
|
198
|
+
labels: {
|
199
|
+
"#{APPENGINE_SERVICE_NAME}/module_id" => MANAGED_VM_BACKEND_NAME,
|
200
|
+
"#{APPENGINE_SERVICE_NAME}/version_id" => MANAGED_VM_BACKEND_VERSION,
|
201
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
202
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
203
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
CONTAINER_TAG = "kubernetes.#{CONTAINER_POD_NAME}_" \
|
208
|
+
"#{CONTAINER_NAMESPACE_NAME}_#{CONTAINER_CONTAINER_NAME}"
|
209
|
+
|
210
|
+
CONTAINER_FROM_METADATA_PARAMS = {
|
211
|
+
service_name: CONTAINER_SERVICE_NAME,
|
212
|
+
log_name: CONTAINER_CONTAINER_NAME,
|
213
|
+
project_id: PROJECT_ID,
|
214
|
+
zone: ZONE,
|
215
|
+
labels: {
|
216
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
217
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CONTAINER_CLUSTER_NAME,
|
218
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_name" => CONTAINER_NAMESPACE_NAME,
|
219
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_id" => CONTAINER_NAMESPACE_ID,
|
220
|
+
"#{CONTAINER_SERVICE_NAME}/pod_name" => CONTAINER_POD_NAME,
|
221
|
+
"#{CONTAINER_SERVICE_NAME}/pod_id" => CONTAINER_POD_ID,
|
222
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => CONTAINER_CONTAINER_NAME,
|
223
|
+
"#{CONTAINER_SERVICE_NAME}/stream" => CONTAINER_STREAM,
|
224
|
+
"label/#{CONTAINER_LABEL_KEY}" => CONTAINER_LABEL_VALUE,
|
225
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
226
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
227
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
# Almost the same as from metadata, but missing namespace_id and pod_id.
|
232
|
+
CONTAINER_FROM_TAG_PARAMS = {
|
233
|
+
service_name: CONTAINER_SERVICE_NAME,
|
234
|
+
log_name: CONTAINER_CONTAINER_NAME,
|
235
|
+
project_id: PROJECT_ID,
|
236
|
+
zone: ZONE,
|
237
|
+
labels: {
|
238
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
239
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CONTAINER_CLUSTER_NAME,
|
240
|
+
"#{CONTAINER_SERVICE_NAME}/namespace_name" => CONTAINER_NAMESPACE_NAME,
|
241
|
+
"#{CONTAINER_SERVICE_NAME}/pod_name" => CONTAINER_POD_NAME,
|
242
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => CONTAINER_CONTAINER_NAME,
|
243
|
+
"#{CONTAINER_SERVICE_NAME}/stream" => CONTAINER_STREAM,
|
244
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
245
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
246
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
CLOUDFUNCTIONS_TAG = "kubernetes.#{CLOUDFUNCTIONS_POD_NAME}_" \
|
251
|
+
"#{CLOUDFUNCTIONS_NAMESPACE_NAME}_" \
|
252
|
+
"#{CLOUDFUNCTIONS_CONTAINER_NAME}"
|
253
|
+
|
254
|
+
CLOUDFUNCTIONS_PARAMS = {
|
255
|
+
service_name: CLOUDFUNCTIONS_SERVICE_NAME,
|
256
|
+
log_name: 'cloud-functions',
|
257
|
+
project_id: PROJECT_ID,
|
258
|
+
zone: ZONE,
|
259
|
+
labels: {
|
260
|
+
'execution_id' => CLOUDFUNCTIONS_EXECUTION_ID,
|
261
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/function_name" =>
|
262
|
+
CLOUDFUNCTIONS_FUNCTION_NAME,
|
263
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/region" => CLOUDFUNCTIONS_REGION,
|
264
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
265
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CLOUDFUNCTIONS_CLUSTER_NAME,
|
266
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
267
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
268
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
269
|
+
}
|
270
|
+
}
|
271
|
+
|
272
|
+
CLOUDFUNCTIONS_TEXT_NOT_MATCHED_PARAMS = {
|
273
|
+
service_name: CLOUDFUNCTIONS_SERVICE_NAME,
|
274
|
+
log_name: 'cloud-functions',
|
275
|
+
project_id: PROJECT_ID,
|
276
|
+
zone: ZONE,
|
277
|
+
labels: {
|
278
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/function_name" =>
|
279
|
+
CLOUDFUNCTIONS_FUNCTION_NAME,
|
280
|
+
"#{CLOUDFUNCTIONS_SERVICE_NAME}/region" => CLOUDFUNCTIONS_REGION,
|
281
|
+
"#{CONTAINER_SERVICE_NAME}/instance_id" => VM_ID,
|
282
|
+
"#{CONTAINER_SERVICE_NAME}/cluster_name" => CLOUDFUNCTIONS_CLUSTER_NAME,
|
283
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
284
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => VM_ID,
|
285
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => HOSTNAME
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
CUSTOM_PARAMS = {
|
290
|
+
service_name: COMPUTE_SERVICE_NAME,
|
291
|
+
log_name: 'test',
|
292
|
+
project_id: CUSTOM_PROJECT_ID,
|
293
|
+
zone: CUSTOM_ZONE,
|
294
|
+
labels: {
|
295
|
+
"#{COMPUTE_SERVICE_NAME}/resource_type" => 'instance',
|
296
|
+
"#{COMPUTE_SERVICE_NAME}/resource_id" => CUSTOM_VM_ID,
|
297
|
+
"#{COMPUTE_SERVICE_NAME}/resource_name" => CUSTOM_HOSTNAME
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
EC2_PARAMS = {
|
302
|
+
service_name: EC2_SERVICE_NAME,
|
303
|
+
log_name: 'test',
|
304
|
+
project_id: EC2_PROJECT_ID,
|
305
|
+
zone: EC2_PREFIXED_ZONE,
|
306
|
+
labels: {
|
307
|
+
"#{EC2_SERVICE_NAME}/resource_type" => 'instance',
|
308
|
+
"#{EC2_SERVICE_NAME}/resource_id" => EC2_VM_ID,
|
309
|
+
"#{EC2_SERVICE_NAME}/account_id" => EC2_ACCOUNT_ID,
|
310
|
+
"#{EC2_SERVICE_NAME}/resource_name" => HOSTNAME
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
HTTP_REQUEST_MESSAGE = {
|
315
|
+
'requestMethod' => 'POST',
|
316
|
+
'requestUrl' => 'http://example/',
|
317
|
+
'requestSize' => 210,
|
318
|
+
'status' => 200,
|
319
|
+
'responseSize' => 65,
|
320
|
+
'userAgent' => 'USER AGENT 1.0',
|
321
|
+
'remoteIp' => '55.55.55.55',
|
322
|
+
'referer' => 'http://referer/',
|
323
|
+
'cacheHit' => true,
|
324
|
+
'cacheValidatedWithOriginServer' => true
|
325
|
+
}
|
326
|
+
|
327
|
+
# Tags and their sanitized and encoded version.
|
328
|
+
VALID_TAGS = {
|
329
|
+
'test' => 'test',
|
330
|
+
'germanß' => 'german%C3%9F',
|
331
|
+
'chinese中' => 'chinese%E4%B8%AD',
|
332
|
+
'specialCharacter/_-.' => 'specialCharacter%2F_-.',
|
333
|
+
'abc@&^$*' => 'abc%40%26%5E%24%2A',
|
334
|
+
'@&^$*' => '%40%26%5E%24%2A'
|
335
|
+
}
|
336
|
+
INVALID_TAGS = {
|
337
|
+
# Non-string tags.
|
338
|
+
123 => '123',
|
339
|
+
1.23 => '1.23',
|
340
|
+
[1, 2, 3] => '%5B1%2C%202%2C%203%5D',
|
341
|
+
{ key: 'value' } => '%7B%22key%22%3D%3E%22value%22%7D',
|
342
|
+
# Non-utf8 string tags.
|
343
|
+
"nonutf8#{[0x92].pack('C*')}" => 'nonutf8%20',
|
344
|
+
"abc#{[0x92].pack('C*')}" => 'abc%20',
|
345
|
+
"#{[0x92].pack('C*')}" => '%20',
|
346
|
+
# Empty string tag.
|
347
|
+
'' => '_'
|
348
|
+
}
|
349
|
+
ALL_TAGS = VALID_TAGS.merge(INVALID_TAGS)
|
350
|
+
|
351
|
+
# Shared tests.
|
352
|
+
|
353
|
+
def test_configure_service_account_application_default
|
354
|
+
setup_gce_metadata_stubs
|
355
|
+
d = create_driver
|
356
|
+
assert_equal HOSTNAME, d.instance.vm_name
|
357
|
+
end
|
358
|
+
|
359
|
+
def test_configure_service_account_private_key
|
360
|
+
# Using out-of-date config method.
|
361
|
+
exception_count = 0
|
362
|
+
begin
|
363
|
+
create_driver(PRIVATE_KEY_CONFIG)
|
364
|
+
rescue Fluent::ConfigError => error
|
365
|
+
assert error.message.include? 'Please remove configuration parameters'
|
366
|
+
exception_count += 1
|
367
|
+
end
|
368
|
+
assert_equal 1, exception_count
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_configure_custom_metadata
|
372
|
+
setup_no_metadata_service_stubs
|
373
|
+
d = create_driver(CUSTOM_METADATA_CONFIG)
|
374
|
+
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
375
|
+
assert_equal CUSTOM_ZONE, d.instance.zone
|
376
|
+
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_configure_invalid_metadata_missing_parts
|
380
|
+
setup_no_metadata_service_stubs
|
381
|
+
Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
|
382
|
+
{ CONFIG_MISSING_METADATA_PROJECT_ID => ['project_id'],
|
383
|
+
CONFIG_MISSING_METADATA_ZONE => ['zone'],
|
384
|
+
CONFIG_MISSING_METADATA_VM_ID => ['vm_id'],
|
385
|
+
CONFIG_MISSING_METADATA_ALL => %w(project_id zone vm_id)
|
386
|
+
}.each_with_index do |(config, parts), index|
|
387
|
+
exception_count = 0
|
388
|
+
begin
|
389
|
+
create_driver(config)
|
390
|
+
rescue Fluent::ConfigError => error
|
391
|
+
assert error.message.include?('Unable to obtain metadata parameters:'),
|
392
|
+
"Index #{index} failed."
|
393
|
+
parts.each do |part|
|
394
|
+
assert error.message.include?(part), "Index #{index} failed."
|
395
|
+
end
|
396
|
+
exception_count += 1
|
397
|
+
end
|
398
|
+
assert_equal 1, exception_count, "Index #{index} failed."
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def test_metadata_loading
|
403
|
+
setup_gce_metadata_stubs
|
404
|
+
d = create_driver
|
405
|
+
d.run
|
406
|
+
assert_equal PROJECT_ID, d.instance.project_id
|
407
|
+
assert_equal ZONE, d.instance.zone
|
408
|
+
assert_equal VM_ID, d.instance.vm_id
|
409
|
+
assert_equal false, d.instance.running_on_managed_vm
|
410
|
+
end
|
411
|
+
|
412
|
+
def test_managed_vm_metadata_loading
|
413
|
+
setup_gce_metadata_stubs
|
414
|
+
setup_managed_vm_metadata_stubs
|
415
|
+
d = create_driver
|
416
|
+
d.run
|
417
|
+
assert_equal PROJECT_ID, d.instance.project_id
|
418
|
+
assert_equal ZONE, d.instance.zone
|
419
|
+
assert_equal VM_ID, d.instance.vm_id
|
420
|
+
assert_equal true, d.instance.running_on_managed_vm
|
421
|
+
assert_equal MANAGED_VM_BACKEND_NAME, d.instance.gae_backend_name
|
422
|
+
assert_equal MANAGED_VM_BACKEND_VERSION, d.instance.gae_backend_version
|
423
|
+
end
|
424
|
+
|
425
|
+
def test_gce_metadata_does_not_load_when_use_metadata_service_is_false
|
426
|
+
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
|
427
|
+
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
|
428
|
+
d.run
|
429
|
+
assert_equal CUSTOM_PROJECT_ID, d.instance.project_id
|
430
|
+
assert_equal CUSTOM_ZONE, d.instance.zone
|
431
|
+
assert_equal CUSTOM_VM_ID, d.instance.vm_id
|
432
|
+
assert_equal false, d.instance.running_on_managed_vm
|
433
|
+
end
|
434
|
+
|
435
|
+
def test_gce_used_when_detect_subservice_is_false
|
436
|
+
setup_gce_metadata_stubs
|
437
|
+
# This would cause the service to be container.googleapis.com if not for the
|
438
|
+
# detect_subservice=false config.
|
439
|
+
setup_container_metadata_stubs
|
440
|
+
d = create_driver(NO_DETECT_SUBSERVICE_CONFIG)
|
441
|
+
d.run
|
442
|
+
assert_equal COMPUTE_SERVICE_NAME, d.instance.service_name
|
443
|
+
end
|
444
|
+
|
445
|
+
def test_metadata_overrides
|
446
|
+
{
|
447
|
+
# In this case we are overriding all configured parameters so we should
|
448
|
+
# see all "custom" values rather than the ones from the metadata server.
|
449
|
+
CUSTOM_METADATA_CONFIG =>
|
450
|
+
['gce', CUSTOM_PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
|
451
|
+
# Similar to above, but we are not overriding project_id in this config so
|
452
|
+
# we should see the metadata value for project_id and "custom" otherwise.
|
453
|
+
CONFIG_MISSING_METADATA_PROJECT_ID =>
|
454
|
+
['gce', PROJECT_ID, CUSTOM_ZONE, CUSTOM_VM_ID],
|
455
|
+
CONFIG_EC2_PROJECT_ID =>
|
456
|
+
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, EC2_VM_ID],
|
457
|
+
CONFIG_EC2_PROJECT_ID_AND_CUSTOM_VM_ID =>
|
458
|
+
['ec2', EC2_PROJECT_ID, EC2_PREFIXED_ZONE, CUSTOM_VM_ID]
|
459
|
+
}.each_with_index do |(config, parts), index|
|
460
|
+
send("setup_#{parts[0]}_metadata_stubs")
|
461
|
+
d = create_driver(config)
|
462
|
+
d.run
|
463
|
+
assert_equal parts[1], d.instance.project_id, "Index #{index} failed."
|
464
|
+
assert_equal parts[2], d.instance.zone, "Index #{index} failed."
|
465
|
+
assert_equal parts[3], d.instance.vm_id, "Index #{index} failed."
|
466
|
+
assert_equal false, d.instance.running_on_managed_vm,
|
467
|
+
"Index #{index} failed."
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def test_ec2_metadata_requires_project_id
|
472
|
+
setup_ec2_metadata_stubs
|
473
|
+
exception_count = 0
|
474
|
+
Fluent::GoogleCloudOutput::CredentialsInfo.stubs(:project_id).returns(nil)
|
475
|
+
begin
|
476
|
+
create_driver
|
477
|
+
rescue Fluent::ConfigError => error
|
478
|
+
assert error.message.include? 'Unable to obtain metadata parameters:'
|
479
|
+
assert error.message.include? 'project_id'
|
480
|
+
exception_count += 1
|
481
|
+
end
|
482
|
+
assert_equal 1, exception_count
|
483
|
+
end
|
484
|
+
|
485
|
+
def test_ec2_metadata_project_id_from_credentials
|
486
|
+
setup_ec2_metadata_stubs
|
487
|
+
[IAM_CREDENTIALS, LEGACY_CREDENTIALS].each do |creds|
|
488
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = creds[:path]
|
489
|
+
d = create_driver
|
490
|
+
d.run
|
491
|
+
assert_equal creds[:project_id], d.instance.project_id
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_one_log
|
496
|
+
setup_gce_metadata_stubs
|
497
|
+
setup_logging_stubs do
|
498
|
+
d = create_driver
|
499
|
+
d.emit('message' => log_entry(0))
|
500
|
+
d.run
|
501
|
+
end
|
502
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
503
|
+
end
|
504
|
+
|
505
|
+
def test_one_log_with_json_credentials
|
506
|
+
setup_gce_metadata_stubs
|
507
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
508
|
+
setup_logging_stubs do
|
509
|
+
d = create_driver
|
510
|
+
d.emit('message' => log_entry(0))
|
511
|
+
d.run
|
512
|
+
end
|
513
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
514
|
+
end
|
515
|
+
|
516
|
+
def test_one_log_with_invalid_json_credentials
|
517
|
+
setup_gce_metadata_stubs
|
518
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = INVALID_CREDENTIALS[:path]
|
519
|
+
setup_logging_stubs do
|
520
|
+
d = create_driver
|
521
|
+
d.emit('message' => log_entry(0))
|
522
|
+
exception_count = 0
|
523
|
+
begin
|
524
|
+
d.run
|
525
|
+
rescue RuntimeError => error
|
526
|
+
assert error.message.include? 'Unable to read the credential file'
|
527
|
+
exception_count += 1
|
528
|
+
end
|
529
|
+
assert_equal 1, exception_count
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
def test_one_log_custom_metadata
|
534
|
+
# don't set up any metadata stubs, so the test will fail if we try to
|
535
|
+
# fetch metadata (and explicitly check this as well).
|
536
|
+
Fluent::GoogleCloudOutput.any_instance.expects(:fetch_metadata).never
|
537
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
538
|
+
setup_logging_stubs do
|
539
|
+
d = create_driver(NO_METADATA_SERVICE_CONFIG + CUSTOM_METADATA_CONFIG)
|
540
|
+
d.emit('message' => log_entry(0))
|
541
|
+
d.run
|
542
|
+
end
|
543
|
+
verify_log_entries(1, CUSTOM_PARAMS)
|
544
|
+
end
|
545
|
+
|
546
|
+
def test_one_log_ec2
|
547
|
+
ENV['GOOGLE_APPLICATION_CREDENTIALS'] = IAM_CREDENTIALS[:path]
|
548
|
+
setup_ec2_metadata_stubs
|
549
|
+
setup_logging_stubs do
|
550
|
+
d = create_driver(CONFIG_EC2_PROJECT_ID)
|
551
|
+
d.emit('message' => log_entry(0))
|
552
|
+
d.run
|
553
|
+
end
|
554
|
+
verify_log_entries(1, EC2_PARAMS)
|
555
|
+
end
|
556
|
+
|
557
|
+
def test_struct_payload_log
|
558
|
+
setup_gce_metadata_stubs
|
559
|
+
setup_logging_stubs do
|
560
|
+
d = create_driver
|
561
|
+
d.emit('msg' => log_entry(0), 'tag2' => 'test', 'data' => 5000,
|
562
|
+
'some_null_field' => nil)
|
563
|
+
d.run
|
564
|
+
end
|
565
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
566
|
+
fields = get_fields(entry['structPayload'])
|
567
|
+
assert_equal 4, fields.size, entry
|
568
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
569
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
570
|
+
assert_equal 5000, get_number(fields['data']), entry
|
571
|
+
assert_equal null_value, fields['some_null_field'], entry
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
def test_struct_payload_malformatted_log
|
576
|
+
setup_gce_metadata_stubs
|
577
|
+
message = 'test message'
|
578
|
+
setup_logging_stubs do
|
579
|
+
d = create_driver
|
580
|
+
d.emit(
|
581
|
+
'int_key' => { 1 => message },
|
582
|
+
'int_array_key' => { [1, 2, 3, 4] => message },
|
583
|
+
'string_array_key' => { %w(a b c) => message },
|
584
|
+
'hash_key' => { { 'some_key' => 'some_value' } => message },
|
585
|
+
'mixed_key' => { { 'some_key' => %w(a b c) } => message },
|
586
|
+
'symbol_key' => { some_symbol: message },
|
587
|
+
'nil_key' => { nil => message }
|
588
|
+
)
|
589
|
+
d.run
|
590
|
+
end
|
591
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
592
|
+
fields = get_fields(entry['structPayload'])
|
593
|
+
assert_equal 7, fields.size, entry
|
594
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
595
|
+
['int_key']))['1']), entry
|
596
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
597
|
+
['int_array_key']))['[1, 2, 3, 4]']), entry
|
598
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
599
|
+
['string_array_key']))['["a", "b", "c"]']), entry
|
600
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
601
|
+
['hash_key']))['{"some_key"=>"some_value"}']), entry
|
602
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
603
|
+
['mixed_key']))['{"some_key"=>["a", "b", "c"]}']), entry
|
604
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
605
|
+
['symbol_key']))['some_symbol']), entry
|
606
|
+
assert_equal message, get_string(get_fields(get_struct(fields \
|
607
|
+
['nil_key']))['']), entry
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def test_struct_payload_json_log
|
612
|
+
setup_gce_metadata_stubs
|
613
|
+
setup_logging_stubs do
|
614
|
+
d = create_driver
|
615
|
+
json_string = '{"msg": "test log entry 0", "tag2": "test", "data": 5000}'
|
616
|
+
d.emit('message' => 'notJSON ' + json_string)
|
617
|
+
d.emit('message' => json_string)
|
618
|
+
d.emit('message' => "\t" + json_string)
|
619
|
+
d.emit('message' => ' ' + json_string)
|
620
|
+
d.run
|
621
|
+
end
|
622
|
+
verify_log_entries(4, COMPUTE_PARAMS, '') do |entry|
|
623
|
+
assert entry.key?('textPayload'), 'Entry did not have textPayload'
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
def test_struct_payload_json_container_log
|
628
|
+
setup_gce_metadata_stubs
|
629
|
+
setup_container_metadata_stubs
|
630
|
+
setup_logging_stubs do
|
631
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
632
|
+
json_string = '{"msg": "test log entry 0", "tag2": "test", ' \
|
633
|
+
'"data": 5000, "some_null_field": null}'
|
634
|
+
d.emit(container_log_entry_with_metadata('notJSON' + json_string))
|
635
|
+
d.emit(container_log_entry_with_metadata(json_string))
|
636
|
+
d.emit(container_log_entry_with_metadata(" \r\n \t" + json_string))
|
637
|
+
d.run
|
638
|
+
end
|
639
|
+
log_index = 0
|
640
|
+
verify_log_entries(
|
641
|
+
3, CONTAINER_FROM_METADATA_PARAMS, '') do |entry|
|
642
|
+
log_index += 1
|
643
|
+
if log_index == 1
|
644
|
+
assert entry.key?('textPayload'), 'Entry did not have textPayload'
|
645
|
+
else
|
646
|
+
assert entry.key?('structPayload'), 'Entry did not have structPayload'
|
647
|
+
fields = get_fields(entry['structPayload'])
|
648
|
+
assert_equal 4, fields.size, entry
|
649
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
650
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
651
|
+
assert_equal 5000, get_number(fields['data']), entry
|
652
|
+
assert_equal null_value, fields['some_null_field'], entry
|
653
|
+
end
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
# Verify that we drop the log entries when 'require_valid_tags' is true and
|
658
|
+
# any non-string tags or tags with non-utf8 characters are detected.
|
659
|
+
def test_reject_invalid_tags_with_require_valid_tags_true
|
660
|
+
setup_gce_metadata_stubs
|
661
|
+
INVALID_TAGS.keys.each do |tag|
|
662
|
+
setup_logging_stubs do
|
663
|
+
@logs_sent = []
|
664
|
+
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
|
665
|
+
d.emit('msg' => log_entry(0))
|
666
|
+
d.run
|
667
|
+
end
|
668
|
+
verify_log_entries(0, COMPUTE_PARAMS, 'structPayload')
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
# Verify that empty string container name should fail the kubernetes regex
|
673
|
+
# match, thus the original tag is used as the log name.
|
674
|
+
def test_handle_empty_container_name
|
675
|
+
setup_gce_metadata_stubs
|
676
|
+
setup_container_metadata_stubs
|
677
|
+
container_name = ''
|
678
|
+
# This tag will not match the kubernetes regex because it requires a
|
679
|
+
# non-empty container name.
|
680
|
+
tag = container_tag_with_container_name(container_name)
|
681
|
+
params = CONTAINER_FROM_METADATA_PARAMS.merge(
|
682
|
+
labels: CONTAINER_FROM_METADATA_PARAMS[:labels].merge(
|
683
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => container_name),
|
684
|
+
log_name: tag)
|
685
|
+
setup_logging_stubs([params]) do
|
686
|
+
@logs_sent = []
|
687
|
+
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
|
688
|
+
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
|
689
|
+
d.run
|
690
|
+
end
|
691
|
+
verify_log_entries(1, params, 'textPayload')
|
692
|
+
assert_equal "projects/#{PROJECT_ID}/logs/#{tag}", @logs_sent[0]['logName']
|
693
|
+
end
|
694
|
+
|
695
|
+
# Verify that container names with non-utf8 characters should be rejected when
|
696
|
+
# 'require_valid_tags' is true.
|
697
|
+
def test_reject_non_utf8_container_name_with_require_valid_tags_true
|
698
|
+
setup_gce_metadata_stubs
|
699
|
+
setup_container_metadata_stubs
|
700
|
+
non_utf8_tags = INVALID_TAGS.select do |tag, _|
|
701
|
+
tag.is_a?(String) && !tag.empty?
|
702
|
+
end
|
703
|
+
non_utf8_tags.each do |container_name, encoded_name|
|
704
|
+
params = CONTAINER_FROM_METADATA_PARAMS.merge(
|
705
|
+
labels: CONTAINER_FROM_METADATA_PARAMS[:labels].merge(
|
706
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" =>
|
707
|
+
URI.decode(encoded_name)),
|
708
|
+
log_name: encoded_name)
|
709
|
+
setup_logging_stubs([params]) do
|
710
|
+
@logs_sent = []
|
711
|
+
d = create_driver(REQUIRE_VALID_TAGS_CONFIG,
|
712
|
+
container_tag_with_container_name(container_name))
|
713
|
+
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
|
714
|
+
d.run
|
715
|
+
end
|
716
|
+
verify_log_entries(0, params, 'textPayload')
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
# Verify that tags are properly encoded. When 'require_valid_tags' is true, we
|
721
|
+
# only accept string tags with utf8 characters.
|
722
|
+
def test_encode_tags_with_require_valid_tags_true
|
723
|
+
setup_gce_metadata_stubs
|
724
|
+
VALID_TAGS.each do |tag, encoded_tag|
|
725
|
+
setup_logging_stubs([COMPUTE_PARAMS.merge(log_name: encoded_tag)]) do
|
726
|
+
@logs_sent = []
|
727
|
+
d = create_driver(REQUIRE_VALID_TAGS_CONFIG, tag)
|
728
|
+
d.emit('msg' => log_entry(0))
|
729
|
+
d.run
|
730
|
+
end
|
731
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload')
|
732
|
+
assert_equal "projects/#{PROJECT_ID}/logs/#{encoded_tag}",
|
733
|
+
@logs_sent[0]['logName']
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
# Verify that tags extracted from container names are properly encoded.
|
738
|
+
def test_encode_tags_from_container_name_with_require_valid_tags_true
|
739
|
+
setup_gce_metadata_stubs
|
740
|
+
setup_container_metadata_stubs
|
741
|
+
VALID_TAGS.each do |tag, encoded_tag|
|
742
|
+
params = CONTAINER_FROM_METADATA_PARAMS.merge(
|
743
|
+
labels: CONTAINER_FROM_METADATA_PARAMS[:labels].merge(
|
744
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" => tag),
|
745
|
+
log_name: encoded_tag)
|
746
|
+
setup_logging_stubs([params]) do
|
747
|
+
@logs_sent = []
|
748
|
+
d = create_driver(REQUIRE_VALID_TAGS_CONFIG,
|
749
|
+
container_tag_with_container_name(tag))
|
750
|
+
d.emit(container_log_entry_with_metadata(log_entry(0), tag))
|
751
|
+
d.run
|
752
|
+
end
|
753
|
+
verify_log_entries(1, params, 'textPayload')
|
754
|
+
assert_equal "projects/#{PROJECT_ID}/logs/#{encoded_tag}",
|
755
|
+
@logs_sent[0]['logName']
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
# Verify that tags are properly encoded and sanitized. When
|
760
|
+
# 'require_valid_tags' is false, we try to convert any non-string tags to
|
761
|
+
# strings, and replace non-utf8 characters with a replacement string.
|
762
|
+
def test_sanitize_tags_with_require_valid_tags_false
|
763
|
+
setup_gce_metadata_stubs
|
764
|
+
ALL_TAGS.each do |tag, sanitized_tag|
|
765
|
+
setup_logging_stubs([COMPUTE_PARAMS.merge(log_name: sanitized_tag)]) do
|
766
|
+
@logs_sent = []
|
767
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, tag)
|
768
|
+
d.emit('msg' => log_entry(0))
|
769
|
+
d.run
|
770
|
+
end
|
771
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload')
|
772
|
+
assert_equal "projects/#{PROJECT_ID}/logs/#{sanitized_tag}",
|
773
|
+
@logs_sent[0]['logName']
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
# Verify that tags extracted from container names are properly encoded and
|
778
|
+
# sanitized.
|
779
|
+
def test_sanitize_tags_from_container_name_with_require_valid_tags_false
|
780
|
+
setup_gce_metadata_stubs
|
781
|
+
setup_container_metadata_stubs
|
782
|
+
# Log names are derived from container names for containers. And container
|
783
|
+
# names are extracted from the tag based on a regex match pattern. As a
|
784
|
+
# prerequisite, the tag should already be a string, thus we only test
|
785
|
+
# non-empty string cases here.
|
786
|
+
string_tags = ALL_TAGS.select { |tag, _| tag.is_a?(String) && !tag.empty? }
|
787
|
+
string_tags.each do |container_name, encoded_container_name|
|
788
|
+
# Container name in the label is sanitized but not encoded, while the log
|
789
|
+
# name is encoded.
|
790
|
+
params = CONTAINER_FROM_METADATA_PARAMS.merge(
|
791
|
+
labels: CONTAINER_FROM_METADATA_PARAMS[:labels].merge(
|
792
|
+
"#{CONTAINER_SERVICE_NAME}/container_name" =>
|
793
|
+
URI.decode(encoded_container_name)),
|
794
|
+
log_name: encoded_container_name)
|
795
|
+
setup_logging_stubs([params]) do
|
796
|
+
@logs_sent = []
|
797
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG,
|
798
|
+
container_tag_with_container_name(container_name))
|
799
|
+
d.emit(container_log_entry_with_metadata(log_entry(0), container_name))
|
800
|
+
d.run
|
801
|
+
end
|
802
|
+
verify_log_entries(1, params, 'textPayload')
|
803
|
+
assert_equal "projects/#{PROJECT_ID}/logs/#{encoded_container_name}",
|
804
|
+
@logs_sent[0]['logName']
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
def test_timestamps
|
809
|
+
setup_gce_metadata_stubs
|
810
|
+
expected_ts = []
|
811
|
+
emit_index = 0
|
812
|
+
setup_logging_stubs do
|
813
|
+
[Time.at(123_456.789), Time.at(0), Time.now].each do |ts|
|
814
|
+
d = create_driver
|
815
|
+
# Test the "native" fluentd timestamp as well as our nanosecond tags.
|
816
|
+
d.emit({ 'message' => log_entry(emit_index) }, ts.to_f)
|
817
|
+
expected_ts.push(ts)
|
818
|
+
emit_index += 1
|
819
|
+
d.emit('message' => log_entry(emit_index),
|
820
|
+
'timeNanos' => ts.tv_sec * 1_000_000_000 + ts.tv_nsec)
|
821
|
+
expected_ts.push(ts)
|
822
|
+
emit_index += 1
|
823
|
+
d.emit('message' => log_entry(emit_index),
|
824
|
+
'timestamp' => { 'seconds' => ts.tv_sec, 'nanos' => ts.tv_nsec })
|
825
|
+
expected_ts.push(ts)
|
826
|
+
emit_index += 1
|
827
|
+
d.emit('message' => log_entry(emit_index),
|
828
|
+
'timestampSeconds' => ts.tv_sec, 'timestampNanos' => ts.tv_nsec)
|
829
|
+
expected_ts.push(ts)
|
830
|
+
emit_index += 1
|
831
|
+
d.run
|
832
|
+
end
|
833
|
+
end
|
834
|
+
verify_index = 0
|
835
|
+
verify_log_entries(emit_index, COMPUTE_PARAMS) do |entry|
|
836
|
+
assert_equal_with_default entry['metadata']['timestamp']['seconds'],
|
837
|
+
expected_ts[verify_index].tv_sec, 0, entry
|
838
|
+
assert_equal_with_default entry['metadata']['timestamp']['nanos'],
|
839
|
+
expected_ts[verify_index].tv_nsec, 0, entry do
|
840
|
+
# Fluentd v0.14 onwards supports nanosecond timestamp values.
|
841
|
+
# Added in 600 ns delta to avoid flaky tests introduced
|
842
|
+
# due to rounding error in double-precision floating-point numbers
|
843
|
+
# (to account for the missing 9 bits of precision ~ 512 ns).
|
844
|
+
# See http://wikipedia.org/wiki/Double-precision_floating-point_format
|
845
|
+
assert_in_delta expected_ts[verify_index].tv_nsec,
|
846
|
+
entry['metadata']['timestamp']['nanos'], 600, entry
|
847
|
+
end
|
848
|
+
verify_index += 1
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
def test_malformed_timestamp
|
853
|
+
setup_gce_metadata_stubs
|
854
|
+
setup_logging_stubs do
|
855
|
+
d = create_driver
|
856
|
+
# if timestamp is not a hash it is passed through to the struct payload.
|
857
|
+
d.emit('message' => log_entry(0), 'timestamp' => 'not-a-hash')
|
858
|
+
d.run
|
859
|
+
end
|
860
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
861
|
+
fields = get_fields(entry['structPayload'])
|
862
|
+
assert_equal 2, fields.size, entry
|
863
|
+
assert_equal 'not-a-hash', get_string(fields['timestamp']), entry
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
# Make parse_severity public so we can test it.
|
868
|
+
class Fluent::GoogleCloudOutput # rubocop:disable Style/ClassAndModuleChildren
|
869
|
+
public :parse_severity
|
870
|
+
end
|
871
|
+
|
872
|
+
def test_label_map_without_field_present
|
873
|
+
setup_gce_metadata_stubs
|
874
|
+
setup_logging_stubs do
|
875
|
+
config = %(label_map { "label_field": "sent_label" })
|
876
|
+
d = create_driver(config)
|
877
|
+
d.emit('message' => log_entry(0))
|
878
|
+
d.run
|
879
|
+
# No additional labels should be present
|
880
|
+
end
|
881
|
+
verify_log_entries(1, COMPUTE_PARAMS)
|
882
|
+
end
|
883
|
+
|
884
|
+
def test_label_map_with_field_present
|
885
|
+
setup_gce_metadata_stubs
|
886
|
+
setup_logging_stubs do
|
887
|
+
config = %(label_map { "label_field": "sent_label" })
|
888
|
+
d = create_driver(config)
|
889
|
+
d.emit('message' => log_entry(0), 'label_field' => 'label_value')
|
890
|
+
d.run
|
891
|
+
end
|
892
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
893
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
894
|
+
params[:labels]['sent_label'] = 'label_value'
|
895
|
+
verify_log_entries(1, params)
|
896
|
+
end
|
897
|
+
|
898
|
+
def test_label_map_with_numeric_field
|
899
|
+
setup_gce_metadata_stubs
|
900
|
+
setup_logging_stubs do
|
901
|
+
config = %(label_map { "label_field": "sent_label" })
|
902
|
+
d = create_driver(config)
|
903
|
+
d.emit('message' => log_entry(0), 'label_field' => 123_456_789)
|
904
|
+
d.run
|
905
|
+
end
|
906
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
907
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
908
|
+
params[:labels]['sent_label'] = '123456789'
|
909
|
+
verify_log_entries(1, params)
|
910
|
+
end
|
911
|
+
|
912
|
+
def test_label_map_with_hash_field
|
913
|
+
setup_gce_metadata_stubs
|
914
|
+
setup_logging_stubs do
|
915
|
+
config = %(label_map { "label_field": "sent_label" })
|
916
|
+
d = create_driver(config)
|
917
|
+
# I'm not sure this actually makes sense for a user to do, but make
|
918
|
+
# sure that it works if they try it.
|
919
|
+
d.emit('message' => log_entry(0),
|
920
|
+
'label_field' => { 'k1' => 10, 'k2' => 'val' })
|
921
|
+
d.run
|
922
|
+
end
|
923
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed label.
|
924
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
925
|
+
params[:labels]['sent_label'] = '{"k1"=>10, "k2"=>"val"}'
|
926
|
+
verify_log_entries(1, params)
|
927
|
+
end
|
928
|
+
|
929
|
+
def test_label_map_with_multiple_fields
|
930
|
+
setup_gce_metadata_stubs
|
931
|
+
setup_logging_stubs do
|
932
|
+
config = %(
|
933
|
+
label_map {
|
934
|
+
"label1": "sent_label_1",
|
935
|
+
"label_number_two": "foo.googleapis.com/bar",
|
936
|
+
"label3": "label3"
|
937
|
+
}
|
938
|
+
)
|
939
|
+
d = create_driver(config)
|
940
|
+
# not_a_label passes through to the struct payload
|
941
|
+
d.emit('message' => log_entry(0),
|
942
|
+
'label1' => 'value1',
|
943
|
+
'label_number_two' => 'value2',
|
944
|
+
'not_a_label' => 'value4',
|
945
|
+
'label3' => 'value3')
|
946
|
+
d.run
|
947
|
+
end
|
948
|
+
# make a deep copy of COMPUTE_PARAMS and add the parsed labels.
|
949
|
+
params = Marshal.load(Marshal.dump(COMPUTE_PARAMS))
|
950
|
+
params[:labels]['sent_label_1'] = 'value1'
|
951
|
+
params[:labels]['foo.googleapis.com/bar'] = 'value2'
|
952
|
+
params[:labels]['label3'] = 'value3'
|
953
|
+
verify_log_entries(1, params, 'structPayload') do |entry|
|
954
|
+
fields = get_fields(entry['structPayload'])
|
955
|
+
assert_equal 2, fields.size, entry
|
956
|
+
assert_equal 'test log entry 0', get_string(fields['message']), entry
|
957
|
+
assert_equal 'value4', get_string(fields['not_a_label']), entry
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
def test_multiple_logs
|
962
|
+
setup_gce_metadata_stubs
|
963
|
+
# Only test a few values because otherwise the test can take minutes.
|
964
|
+
[2, 3, 5, 11, 50].each do |n|
|
965
|
+
setup_logging_stubs do
|
966
|
+
d = create_driver
|
967
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
968
|
+
# do it manually here.
|
969
|
+
d.instance_variable_get('@entries').clear
|
970
|
+
@logs_sent = []
|
971
|
+
n.times { |i| d.emit('message' => log_entry(i)) }
|
972
|
+
d.run
|
973
|
+
end
|
974
|
+
verify_log_entries(n, COMPUTE_PARAMS)
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
def test_malformed_log
|
979
|
+
setup_gce_metadata_stubs
|
980
|
+
setup_logging_stubs do
|
981
|
+
d = create_driver
|
982
|
+
# if the entry is not a hash, the plugin should silently drop it.
|
983
|
+
d.emit('a string is not a valid message')
|
984
|
+
d.run
|
985
|
+
end
|
986
|
+
assert @logs_sent.empty?
|
987
|
+
end
|
988
|
+
|
989
|
+
def test_one_managed_vm_log
|
990
|
+
setup_gce_metadata_stubs
|
991
|
+
setup_managed_vm_metadata_stubs
|
992
|
+
setup_logging_stubs do
|
993
|
+
d = create_driver
|
994
|
+
d.emit('message' => log_entry(0))
|
995
|
+
d.run
|
996
|
+
end
|
997
|
+
verify_log_entries(1, VMENGINE_PARAMS)
|
998
|
+
end
|
999
|
+
|
1000
|
+
def test_multiple_managed_vm_logs
|
1001
|
+
setup_gce_metadata_stubs
|
1002
|
+
setup_managed_vm_metadata_stubs
|
1003
|
+
[2, 3, 5, 11, 50].each do |n|
|
1004
|
+
setup_logging_stubs do
|
1005
|
+
d = create_driver
|
1006
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1007
|
+
# do it manually here.
|
1008
|
+
d.instance_variable_get('@entries').clear
|
1009
|
+
@logs_sent = []
|
1010
|
+
n.times { |i| d.emit('message' => log_entry(i)) }
|
1011
|
+
d.run
|
1012
|
+
end
|
1013
|
+
verify_log_entries(n, VMENGINE_PARAMS)
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def test_one_container_log_metadata_from_plugin
|
1018
|
+
setup_gce_metadata_stubs
|
1019
|
+
setup_container_metadata_stubs
|
1020
|
+
setup_logging_stubs do
|
1021
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1022
|
+
d.emit(container_log_entry_with_metadata(log_entry(0)))
|
1023
|
+
d.run
|
1024
|
+
end
|
1025
|
+
verify_log_entries(1, CONTAINER_FROM_METADATA_PARAMS) do |entry|
|
1026
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1027
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1028
|
+
assert_equal CONTAINER_NANOS, \
|
1029
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1030
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def test_multiple_container_logs_metadata_from_plugin
|
1035
|
+
setup_gce_metadata_stubs
|
1036
|
+
setup_container_metadata_stubs
|
1037
|
+
[2, 3, 5, 11, 50].each do |n|
|
1038
|
+
@logs_sent = []
|
1039
|
+
setup_logging_stubs do
|
1040
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1041
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1042
|
+
# do it manually here.
|
1043
|
+
d.instance_variable_get('@entries').clear
|
1044
|
+
n.times { |i| d.emit(container_log_entry_with_metadata(log_entry(i))) }
|
1045
|
+
d.run
|
1046
|
+
end
|
1047
|
+
verify_log_entries(n, CONTAINER_FROM_METADATA_PARAMS) do |entry|
|
1048
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1049
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1050
|
+
assert_equal CONTAINER_NANOS, \
|
1051
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1052
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def test_multiple_container_logs_metadata_from_tag
|
1058
|
+
setup_gce_metadata_stubs
|
1059
|
+
setup_container_metadata_stubs
|
1060
|
+
[2, 3, 5, 11, 50].each do |n|
|
1061
|
+
@logs_sent = []
|
1062
|
+
setup_logging_stubs do
|
1063
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1064
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1065
|
+
# do it manually here.
|
1066
|
+
d.instance_variable_get('@entries').clear
|
1067
|
+
n.times { |i| d.emit(container_log_entry(log_entry(i))) }
|
1068
|
+
d.run
|
1069
|
+
end
|
1070
|
+
verify_log_entries(n, CONTAINER_FROM_TAG_PARAMS) do |entry|
|
1071
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1072
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1073
|
+
assert_equal CONTAINER_NANOS, \
|
1074
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1075
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def test_one_container_log_metadata_from_tag
|
1081
|
+
setup_gce_metadata_stubs
|
1082
|
+
setup_container_metadata_stubs
|
1083
|
+
setup_logging_stubs do
|
1084
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1085
|
+
d.emit(container_log_entry(log_entry(0)))
|
1086
|
+
d.run
|
1087
|
+
end
|
1088
|
+
verify_log_entries(1, CONTAINER_FROM_TAG_PARAMS) do |entry|
|
1089
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1090
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1091
|
+
assert_equal CONTAINER_NANOS, \
|
1092
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1093
|
+
assert_equal CONTAINER_SEVERITY, entry['metadata']['severity'], entry
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def test_one_container_log_from_tag_stderr
|
1098
|
+
setup_gce_metadata_stubs
|
1099
|
+
setup_container_metadata_stubs
|
1100
|
+
setup_logging_stubs do
|
1101
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1102
|
+
d.emit(container_log_entry(log_entry(0), 'stderr'))
|
1103
|
+
d.run
|
1104
|
+
end
|
1105
|
+
expected_params = CONTAINER_FROM_TAG_PARAMS.merge(
|
1106
|
+
labels: { "#{CONTAINER_SERVICE_NAME}/stream" => 'stderr' }
|
1107
|
+
) { |_, oldval, newval| oldval.merge(newval) }
|
1108
|
+
verify_log_entries(1, expected_params) do |entry|
|
1109
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1110
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1111
|
+
assert_equal CONTAINER_NANOS, \
|
1112
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1113
|
+
assert_equal 'ERROR', entry['metadata']['severity'], entry
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def test_struct_container_log_metadata_from_plugin
|
1118
|
+
setup_gce_metadata_stubs
|
1119
|
+
setup_container_metadata_stubs
|
1120
|
+
setup_logging_stubs do
|
1121
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1122
|
+
d.emit(container_log_entry_with_metadata('{"msg": "test log entry 0", ' \
|
1123
|
+
'"tag2": "test", "data": ' \
|
1124
|
+
'5000, "severity": "WARNING"}'))
|
1125
|
+
d.run
|
1126
|
+
end
|
1127
|
+
verify_log_entries(1, CONTAINER_FROM_METADATA_PARAMS,
|
1128
|
+
'structPayload') do |entry|
|
1129
|
+
fields = get_fields(entry['structPayload'])
|
1130
|
+
assert_equal 3, fields.size, entry
|
1131
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
1132
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
1133
|
+
assert_equal 5000, get_number(fields['data']), entry
|
1134
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1135
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1136
|
+
assert_equal CONTAINER_NANOS, \
|
1137
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1138
|
+
assert_equal 'WARNING', entry['metadata']['severity'], entry
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
def test_struct_container_log_metadata_from_tag
|
1143
|
+
setup_gce_metadata_stubs
|
1144
|
+
setup_container_metadata_stubs
|
1145
|
+
setup_logging_stubs do
|
1146
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1147
|
+
d.emit(container_log_entry('{"msg": "test log entry 0", ' \
|
1148
|
+
'"tag2": "test", "data": 5000, ' \
|
1149
|
+
'"severity": "W"}'))
|
1150
|
+
d.run
|
1151
|
+
end
|
1152
|
+
verify_log_entries(1, CONTAINER_FROM_TAG_PARAMS,
|
1153
|
+
'structPayload') do |entry|
|
1154
|
+
fields = get_fields(entry['structPayload'])
|
1155
|
+
assert_equal 3, fields.size, entry
|
1156
|
+
assert_equal 'test log entry 0', get_string(fields['msg']), entry
|
1157
|
+
assert_equal 'test', get_string(fields['tag2']), entry
|
1158
|
+
assert_equal 5000, get_number(fields['data']), entry
|
1159
|
+
assert_equal CONTAINER_SECONDS_EPOCH, \
|
1160
|
+
entry['metadata']['timestamp']['seconds'], entry
|
1161
|
+
assert_equal CONTAINER_NANOS, \
|
1162
|
+
entry['metadata']['timestamp']['nanos'], entry
|
1163
|
+
assert_equal 'WARNING', entry['metadata']['severity'], entry
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
def test_cloudfunctions_log
|
1168
|
+
setup_gce_metadata_stubs
|
1169
|
+
setup_cloudfunctions_metadata_stubs
|
1170
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
1171
|
+
setup_logging_stubs do
|
1172
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CLOUDFUNCTIONS_TAG)
|
1173
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1174
|
+
# do it manually here.
|
1175
|
+
d.instance_variable_get('@entries').clear
|
1176
|
+
@logs_sent = []
|
1177
|
+
n.times { |i| d.emit(cloudfunctions_log_entry(i)) }
|
1178
|
+
d.run
|
1179
|
+
end
|
1180
|
+
verify_log_entries(n, CLOUDFUNCTIONS_PARAMS) do |entry|
|
1181
|
+
assert_equal 'DEBUG', entry['metadata']['severity'],
|
1182
|
+
"Test with #{n} logs failed. \n#{entry}"
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
def test_cloudfunctions_logs_text_not_matched
|
1188
|
+
setup_gce_metadata_stubs
|
1189
|
+
setup_cloudfunctions_metadata_stubs
|
1190
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
1191
|
+
@logs_sent = []
|
1192
|
+
setup_logging_stubs do
|
1193
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CLOUDFUNCTIONS_TAG)
|
1194
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1195
|
+
# do it manually here.
|
1196
|
+
d.instance_variable_get('@entries').clear
|
1197
|
+
n.times { |i| d.emit(cloudfunctions_log_entry_text_not_matched(i)) }
|
1198
|
+
d.run
|
1199
|
+
end
|
1200
|
+
verify_log_entries(
|
1201
|
+
n, CLOUDFUNCTIONS_TEXT_NOT_MATCHED_PARAMS) do |entry|
|
1202
|
+
assert_equal 'INFO', entry['metadata']['severity'],
|
1203
|
+
"Test with #{n} logs failed. \n#{entry}"
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
def test_multiple_cloudfunctions_logs_tag_not_matched
|
1209
|
+
setup_gce_metadata_stubs
|
1210
|
+
setup_cloudfunctions_metadata_stubs
|
1211
|
+
[1, 2, 3, 5, 11, 50].each do |n|
|
1212
|
+
@logs_sent = []
|
1213
|
+
setup_logging_stubs do
|
1214
|
+
d = create_driver(APPLICATION_DEFAULT_CONFIG, CONTAINER_TAG)
|
1215
|
+
# The test driver doesn't clear its buffer of entries after running, so
|
1216
|
+
# do it manually here.
|
1217
|
+
d.instance_variable_get('@entries').clear
|
1218
|
+
n.times { |i| d.emit(cloudfunctions_log_entry(i)) }
|
1219
|
+
d.run
|
1220
|
+
end
|
1221
|
+
i = 0
|
1222
|
+
verify_log_entries(n, CONTAINER_FROM_TAG_PARAMS, '') do |entry|
|
1223
|
+
assert_equal '[D][2015-09-25T12:34:56.789Z][123-0] test log entry ' \
|
1224
|
+
"#{i}", entry['textPayload'],
|
1225
|
+
"Test with #{n} logs failed. \n#{entry}"
|
1226
|
+
i += 1
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
def test_http_request_from_record
|
1232
|
+
setup_gce_metadata_stubs
|
1233
|
+
setup_logging_stubs do
|
1234
|
+
d = create_driver
|
1235
|
+
d.emit('httpRequest' => http_request_message)
|
1236
|
+
d.run
|
1237
|
+
end
|
1238
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
1239
|
+
assert_equal http_request_message, entry['httpRequest'], entry
|
1240
|
+
assert_nil get_fields(entry['structPayload'])['httpRequest'], entry
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
|
1244
|
+
def test_http_request_partial_from_record
|
1245
|
+
setup_gce_metadata_stubs
|
1246
|
+
setup_logging_stubs do
|
1247
|
+
d = create_driver
|
1248
|
+
d.emit('httpRequest' => http_request_message.merge(
|
1249
|
+
'otherKey' => 'value'))
|
1250
|
+
d.run
|
1251
|
+
end
|
1252
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'httpRequest') do |entry|
|
1253
|
+
assert_equal http_request_message, entry['httpRequest'], entry
|
1254
|
+
fields = get_fields(entry['structPayload'])
|
1255
|
+
request = get_fields(get_struct(fields['httpRequest']))
|
1256
|
+
assert_equal 'value', get_string(request['otherKey']), entry
|
1257
|
+
end
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
def test_http_request_when_not_hash
|
1261
|
+
setup_gce_metadata_stubs
|
1262
|
+
setup_logging_stubs do
|
1263
|
+
d = create_driver
|
1264
|
+
d.emit('httpRequest' => 'a_string')
|
1265
|
+
d.run
|
1266
|
+
end
|
1267
|
+
verify_log_entries(1, COMPUTE_PARAMS, 'structPayload') do |entry|
|
1268
|
+
fields = get_fields(entry['structPayload'])
|
1269
|
+
assert_equal 'a_string', get_string(fields['httpRequest']), entry
|
1270
|
+
assert_nil entry['httpRequest'], entry
|
1271
|
+
end
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
private
|
1275
|
+
|
1276
|
+
def uri_for_log(params)
|
1277
|
+
'https://logging.googleapis.com/v1beta3/projects/' + params[:project_id] +
|
1278
|
+
'/logs/' + params[:log_name] + '/entries:write'
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
def stub_metadata_request(metadata_path, response_body)
|
1282
|
+
stub_request(:get, 'http://169.254.169.254/computeMetadata/v1/' +
|
1283
|
+
metadata_path)
|
1284
|
+
.to_return(body: response_body, status: 200,
|
1285
|
+
headers: { 'Content-Length' => response_body.length })
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
def setup_no_metadata_service_stubs
|
1289
|
+
# Simulate a machine with no metadata service present
|
1290
|
+
stub_request(:any, %r{http://169.254.169.254/.*})
|
1291
|
+
.to_raise(Errno::EHOSTUNREACH)
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
def setup_gce_metadata_stubs
|
1295
|
+
# Stub the root, used for platform detection by the plugin and 'googleauth'.
|
1296
|
+
stub_request(:get, 'http://169.254.169.254')
|
1297
|
+
.to_return(status: 200, headers: { 'Metadata-Flavor' => 'Google' })
|
1298
|
+
|
1299
|
+
# Create stubs for all the GCE metadata lookups the agent needs to make.
|
1300
|
+
stub_metadata_request('project/project-id', PROJECT_ID)
|
1301
|
+
stub_metadata_request('instance/zone', FULLY_QUALIFIED_ZONE)
|
1302
|
+
stub_metadata_request('instance/id', VM_ID)
|
1303
|
+
stub_metadata_request('instance/attributes/',
|
1304
|
+
"attribute1\nattribute2\nattribute3")
|
1305
|
+
|
1306
|
+
# Used by 'googleauth' to fetch the default service account credentials.
|
1307
|
+
stub_request(:get, 'http://169.254.169.254/computeMetadata/v1/' \
|
1308
|
+
'instance/service-accounts/default/token')
|
1309
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1310
|
+
status: 200,
|
1311
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1312
|
+
'Content-Type' => 'application/json' })
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
def setup_ec2_metadata_stubs
|
1316
|
+
# Stub the root, used for platform detection.
|
1317
|
+
stub_request(:get, 'http://169.254.169.254')
|
1318
|
+
.to_return(status: 200, headers: { 'Server' => 'EC2ws' })
|
1319
|
+
|
1320
|
+
# Stub the identity document lookup made by the agent.
|
1321
|
+
stub_request(:get, 'http://169.254.169.254/latest/dynamic/' \
|
1322
|
+
'instance-identity/document')
|
1323
|
+
.to_return(body: EC2_IDENTITY_DOCUMENT, status: 200,
|
1324
|
+
headers: { 'Content-Length' => EC2_IDENTITY_DOCUMENT.length })
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
def setup_auth_stubs
|
1328
|
+
# Used when loading credentials from a JSON file.
|
1329
|
+
stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token')
|
1330
|
+
.with(body: hash_including(grant_type: AUTH_GRANT_TYPE))
|
1331
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1332
|
+
status: 200,
|
1333
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1334
|
+
'Content-Type' => 'application/json' })
|
1335
|
+
|
1336
|
+
stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token')
|
1337
|
+
.with(body: hash_including(grant_type: 'refresh_token'))
|
1338
|
+
.to_return(body: %({"access_token": "#{FAKE_AUTH_TOKEN}"}),
|
1339
|
+
status: 200,
|
1340
|
+
headers: { 'Content-Length' => FAKE_AUTH_TOKEN.length,
|
1341
|
+
'Content-Type' => 'application/json' })
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
def setup_managed_vm_metadata_stubs
|
1345
|
+
stub_metadata_request(
|
1346
|
+
'instance/attributes/',
|
1347
|
+
"attribute1\ngae_backend_name\ngae_backend_version\nlast_attribute")
|
1348
|
+
stub_metadata_request('instance/attributes/gae_backend_name',
|
1349
|
+
MANAGED_VM_BACKEND_NAME)
|
1350
|
+
stub_metadata_request('instance/attributes/gae_backend_version',
|
1351
|
+
MANAGED_VM_BACKEND_VERSION)
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
def setup_container_metadata_stubs
|
1355
|
+
stub_metadata_request(
|
1356
|
+
'instance/attributes/',
|
1357
|
+
"attribute1\nkube-env\nlast_attribute")
|
1358
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1359
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1360
|
+
'INSTANCE_PREFIX: '\
|
1361
|
+
"gke-#{CONTAINER_CLUSTER_NAME}-740fdafa\n"\
|
1362
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def setup_cloudfunctions_metadata_stubs
|
1366
|
+
stub_metadata_request(
|
1367
|
+
'instance/attributes/',
|
1368
|
+
"attribute1\nkube-env\ngcf_region\nlast_attribute")
|
1369
|
+
stub_metadata_request('instance/attributes/kube-env',
|
1370
|
+
"ENABLE_NODE_LOGGING: \"true\"\n"\
|
1371
|
+
'INSTANCE_PREFIX: '\
|
1372
|
+
"gke-#{CLOUDFUNCTIONS_CLUSTER_NAME}-740fdafa\n"\
|
1373
|
+
'KUBE_BEARER_TOKEN: AoQiMuwkNP2BMT0S')
|
1374
|
+
stub_metadata_request('instance/attributes/gcf_region',
|
1375
|
+
CLOUDFUNCTIONS_REGION)
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
def container_tag_with_container_name(container_name)
|
1379
|
+
"kubernetes.#{CONTAINER_POD_NAME}_#{CONTAINER_NAMESPACE_NAME}_" \
|
1380
|
+
"#{container_name}"
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
def container_log_entry_with_metadata(
|
1384
|
+
log, container_name = CONTAINER_CONTAINER_NAME)
|
1385
|
+
{
|
1386
|
+
log: log,
|
1387
|
+
stream: CONTAINER_STREAM,
|
1388
|
+
time: CONTAINER_TIMESTAMP,
|
1389
|
+
kubernetes: {
|
1390
|
+
namespace_id: CONTAINER_NAMESPACE_ID,
|
1391
|
+
namespace_name: CONTAINER_NAMESPACE_NAME,
|
1392
|
+
pod_id: CONTAINER_POD_ID,
|
1393
|
+
pod_name: CONTAINER_POD_NAME,
|
1394
|
+
container_name: container_name,
|
1395
|
+
labels: {
|
1396
|
+
CONTAINER_LABEL_KEY => CONTAINER_LABEL_VALUE
|
1397
|
+
}
|
1398
|
+
}
|
1399
|
+
}
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
def container_log_entry(log, stream = CONTAINER_STREAM)
|
1403
|
+
{
|
1404
|
+
log: log,
|
1405
|
+
stream: stream,
|
1406
|
+
time: CONTAINER_TIMESTAMP
|
1407
|
+
}
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
def cloudfunctions_log_entry(i)
|
1411
|
+
{
|
1412
|
+
stream: 'stdout',
|
1413
|
+
log: '[D][2015-09-25T12:34:56.789Z][123-0] ' + log_entry(i)
|
1414
|
+
}
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
def cloudfunctions_log_entry_text_not_matched(i)
|
1418
|
+
{
|
1419
|
+
stream: 'stdout',
|
1420
|
+
log: log_entry(i)
|
1421
|
+
}
|
1422
|
+
end
|
1423
|
+
|
1424
|
+
def log_entry(i)
|
1425
|
+
'test log entry ' + i.to_s
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
def check_labels(entry, common_labels, expected_labels)
|
1429
|
+
# TODO(salty) test/handle overlap between common_labels and entry labels
|
1430
|
+
all_labels ||= common_labels
|
1431
|
+
all_labels.merge!(entry['metadata']['labels'] || {})
|
1432
|
+
all_labels.each do |key, value|
|
1433
|
+
assert value.is_a?(String), "Value #{value} for label #{key} " \
|
1434
|
+
'is not a string: ' + value.class.name
|
1435
|
+
assert expected_labels.key?(key), "Unexpected label #{key} => #{value}"
|
1436
|
+
assert_equal expected_labels[key], value, 'Value mismatch - expected ' \
|
1437
|
+
"#{expected_labels[key]} in #{key} => #{value}"
|
1438
|
+
end
|
1439
|
+
assert_equal expected_labels.length, all_labels.length, 'Expected ' \
|
1440
|
+
"#{expected_labels.length} labels, got #{all_labels.length}"
|
1441
|
+
end
|
1442
|
+
|
1443
|
+
# The caller can optionally provide a block which is called for each entry.
|
1444
|
+
def verify_json_log_entries(n, params, payload_type = 'textPayload')
|
1445
|
+
i = 0
|
1446
|
+
@logs_sent.each do |batch|
|
1447
|
+
batch['entries'].each do |entry|
|
1448
|
+
unless payload_type.empty?
|
1449
|
+
assert entry.key?(payload_type), 'Entry did not contain expected ' \
|
1450
|
+
"#{payload_type} key: " + entry.to_s
|
1451
|
+
# Check the payload for textPayload, otherwise it's up to the caller.
|
1452
|
+
if payload_type == 'textPayload'
|
1453
|
+
assert_equal "test log entry #{i}", entry['textPayload'], batch
|
1454
|
+
end
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
assert_equal params[:zone], entry['metadata']['zone']
|
1458
|
+
assert_equal params[:service_name], entry['metadata']['serviceName']
|
1459
|
+
check_labels entry, batch['commonLabels'], params[:labels]
|
1460
|
+
yield(entry) if block_given?
|
1461
|
+
i += 1
|
1462
|
+
assert i <= n, "Number of entries #{i} exceeds expected number #{n}"
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
assert i == n, "Number of entries #{i} does not match expected number #{n}"
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
# The http request message to test against.
|
1469
|
+
def http_request_message
|
1470
|
+
HTTP_REQUEST_MESSAGE
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
# Replace the 'referer' field with nil.
|
1474
|
+
def http_request_message_with_nil_referer
|
1475
|
+
http_request_message.merge('referer' => nil)
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
# This module expects the methods below to be overridden.
|
1479
|
+
|
1480
|
+
# Create a Fluentd output test driver with the Google Cloud Output plugin.
|
1481
|
+
def create_driver(_conf = APPLICATION_DEFAULT_CONFIG, _tag = 'test')
|
1482
|
+
_undefined
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
# Set up http or grpc stubs to mock the external calls.
|
1486
|
+
def setup_logging_stubs
|
1487
|
+
_undefined
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
# Verify the number and the content of the log entries match the expectation.
|
1491
|
+
# The caller can optionally provide a block which is called for each entry.
|
1492
|
+
def verify_log_entries(_n, _params, _payload_type = 'textPayload', &_block)
|
1493
|
+
_undefined
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
# For an optional field with default values, Protobuf omits the field when it
|
1497
|
+
# is deserialized to json. So we need to add an extra check for gRPC which
|
1498
|
+
# uses Protobuf.
|
1499
|
+
#
|
1500
|
+
# An optional block can be passed in if we need to assert something other than
|
1501
|
+
# a plain equal. e.g. assert_in_delta.
|
1502
|
+
def assert_equal_with_default(_field, _expected_value, _default_value, _entry)
|
1503
|
+
_undefined
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
# Get the fields of the struct payload.
|
1507
|
+
def get_fields(_struct_payload)
|
1508
|
+
_undefined
|
1509
|
+
end
|
1510
|
+
|
1511
|
+
# Get the value of a struct field.
|
1512
|
+
def get_struct(_field)
|
1513
|
+
_undefined
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
# Get the value of a string field.
|
1517
|
+
def get_string(_field)
|
1518
|
+
_undefined
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
# Get the value of a number field.
|
1522
|
+
def get_number(_field)
|
1523
|
+
_undefined
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
# The null value.
|
1527
|
+
def null_value(_field)
|
1528
|
+
_undefined
|
1529
|
+
end
|
1530
|
+
|
1531
|
+
def _undefined
|
1532
|
+
fail "Method #{__callee__} is unimplemented and needs to be overridden."
|
1533
|
+
end
|
1534
|
+
end
|