fluent-plugin-kubernetes_metadata_filter 0.33.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5a3f2f0bcabc1a6f56b3edf41aa6ca50475eac2
4
- data.tar.gz: d20a1f8f5e75366fffab4cb256540c8241afc2d7
3
+ metadata.gz: b64ea97cd4df7be745b06c0f46e82f152e0503ea
4
+ data.tar.gz: b026a7a6039cd7f15aa938be5969fa7e5a818c7b
5
5
  SHA512:
6
- metadata.gz: f9733a0745ac08b92ea4ec8b6af1c414bdab974608b66ffffe5478c2a7171a37cd91dcbf4bea309aa4000d1bb4516d7169e69d7906635a333342ea6ba622f582
7
- data.tar.gz: 75e087d65d6a28039ae945d7fa11196aa93f337269dfa0c368fd07e8d35953f8abd1e1bbdb6d50b66c6a9669fa30818949471c766d06069ef1f7a59041efdeb1
6
+ metadata.gz: 53f23402783e6a0b7857698b99a3a7dfd0e343f4ccd5b7ea2c440d8c3fc624d61c4ef89846aa2c91b90995e8df7feb408df7cd5f933d577b3bcfe76e5ced5174
7
+ data.tar.gz: 912ffc8b2ba2e376bb0ffffe9bd9a881cf2ce6f535328e2c76a53941558d0c7e2209a2359ba319d3d8deb0278252673c15b84c169464bfd8dfc8aa5101adacf8
data/README.md CHANGED
@@ -3,6 +3,14 @@
3
3
  [![Code Climate](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter/badges/gpa.svg)](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter)
4
4
  [![Test Coverage](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter/badges/coverage.svg)](https://codeclimate.com/github/fabric8io/fluent-plugin-kubernetes_metadata_filter)
5
5
 
6
+ The Kubernetes metadata plugin filter enriches container log records with pod and namespace metadata.
7
+
8
+ This plugin derives basic metadata about the container that emitted a given log record using the source of the log record. Records from journald provide metadata about the
9
+ container environment as named fields. Records from JSON files encode metadata about the container in the file name. The initial metadata derived from the source is used
10
+ to lookup additional metadata about the container's associated pod and namespace (e.g. UUIDs, labels, annotations) when the kubernetes_url is configured. If the plugin cannot
11
+ authoritatively determine the namespace of the container emitting a log record, it will use an 'orphan' namespace ID in the metadata. This behaviors supports multi-tenant systems
12
+ that rely on the authenticity of the namespace for proper log isolation.
13
+
6
14
  ## Installation
7
15
 
8
16
  gem install fluent-plugin-kubernetes_metadata_filter
@@ -33,6 +41,10 @@ This must used named capture groups for `container_name`, `pod_name` & `namespac
33
41
  * `include_namespace_metadata` - Collect metadata about namespace such as uid, labels and annotations (default: `false`).
34
42
  Note that when this option is enabled, all label from namespace are collected, but annotations are collected according to `annotation_match` value. If `annotation_match` is not presented, no annotations will be collected from the namespace.
35
43
  * `annotation_match` - Array of regular expressions matching annotation field names. Matched annotations are added to a log record.
44
+ * `allow_orphans` - Modify the namespace and namespace id to the values of `orphaned_namespace_name` and `orphaned_namespace_id`
45
+ when true (default: `true`)
46
+ * `orphaned_namespace_name` - The namespace to associate with records where the namespace can not be determined (default: `.orphaned`)
47
+ * `orphaned_namespace_id` - The namespace id to associate with records where the namespace can not be determined (default: `orphaned`)
36
48
 
37
49
  Reading from the JSON formatted log files with `in_tail` and wildcard filenames:
38
50
  ```
data/Rakefile CHANGED
@@ -23,11 +23,11 @@ task :headers do
23
23
  require 'copyright_header'
24
24
 
25
25
  args = {
26
- :license => 'ASL2',
26
+ :license => 'Apache-2.0',
27
27
  :copyright_software => 'Fluentd Kubernetes Metadata Filter Plugin',
28
28
  :copyright_software_description => 'Enrich Fluentd events with Kubernetes metadata',
29
29
  :copyright_holders => ['Red Hat, Inc.'],
30
- :copyright_years => ['2015'],
30
+ :copyright_years => ['2015-2017'],
31
31
  :add_path => 'lib:test',
32
32
  :output_dir => '.'
33
33
  }
@@ -4,13 +4,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "fluent-plugin-kubernetes_metadata_filter"
7
- gem.version = "0.33.0"
7
+ gem.version = "1.0.0"
8
8
  gem.authors = ["Jimmi Dyson"]
9
9
  gem.email = ["jimmidyson@gmail.com"]
10
10
  gem.description = %q{Filter plugin to add Kubernetes metadata}
11
- gem.summary = %q{Filter plugin to add Kubernetes metadata}
11
+ gem.summary = %q{Fluentd filter plugin to add Kubernetes metadata}
12
12
  gem.homepage = "https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter"
13
- gem.license = "ASL2"
13
+ gem.license = "Apache-2.0"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -2,7 +2,7 @@
2
2
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
3
  # Kubernetes metadata
4
4
  #
5
- # Copyright 2015 Red Hat, Inc.
5
+ # Copyright 2017 Red Hat, Inc.
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
@@ -16,12 +16,20 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  #
19
+
19
20
  require_relative 'kubernetes_metadata_stats'
21
+ require_relative 'kubernetes_metadata_watch_namespaces'
22
+ require_relative 'kubernetes_metadata_watch_pods'
23
+
20
24
  module Fluent
21
25
  class KubernetesMetadataFilter < Fluent::Filter
22
26
  K8_POD_CA_CERT = 'ca.crt'
23
27
  K8_POD_TOKEN = 'token'
24
28
 
29
+ include KubernetesMetadata::Common
30
+ include KubernetesMetadata::WatchNamespaces
31
+ include KubernetesMetadata::WatchPods
32
+
25
33
  Fluent::Plugin.register_filter('kubernetes_metadata', self)
26
34
 
27
35
  config_param :kubernetes_url, :string, default: nil
@@ -39,8 +47,6 @@ module Fluent
39
47
  config_param :bearer_token_file, :string, default: nil
40
48
  config_param :merge_json_log, :bool, default: true
41
49
  config_param :preserve_json_log, :bool, default: true
42
- config_param :include_namespace_id, :bool, default: false
43
- config_param :include_namespace_metadata, :bool, default: false
44
50
  config_param :secret_dir, :string, default: '/var/run/secrets/kubernetes.io/serviceaccount'
45
51
  config_param :de_dot, :bool, default: true
46
52
  config_param :de_dot_separator, :string, default: '_'
@@ -59,77 +65,36 @@ module Fluent
59
65
 
60
66
  config_param :annotation_match, :array, default: []
61
67
  config_param :stats_interval, :integer, default: 30
68
+ config_param :allow_orphans, :bool, default: true
69
+ config_param :orphaned_namespace_name, :string, default: '.orphaned'
70
+ config_param :orphaned_namespace_id, :string, default: 'orphaned'
62
71
 
63
- def syms_to_strs(hsh)
64
- newhsh = {}
65
- hsh.each_pair do |kk,vv|
66
- if vv.is_a?(Hash)
67
- vv = syms_to_strs(vv)
68
- end
69
- if kk.is_a?(Symbol)
70
- newhsh[kk.to_s] = vv
71
- else
72
- newhsh[kk] = vv
73
- end
74
- end
75
- newhsh
76
- end
77
-
78
- def parse_pod_metadata(pod_object)
79
- labels = syms_to_strs(pod_object['metadata']['labels'].to_h)
80
- annotations = match_annotations(syms_to_strs(pod_object['metadata']['annotations'].to_h))
81
- if @de_dot
82
- self.de_dot!(labels)
83
- self.de_dot!(annotations)
84
- end
85
- kubernetes_metadata = {
86
- 'namespace_name' => pod_object['metadata']['namespace'],
87
- 'pod_id' => pod_object['metadata']['uid'],
88
- 'pod_name' => pod_object['metadata']['name'],
89
- 'labels' => labels,
90
- 'host' => pod_object['spec']['nodeName'],
91
- 'master_url' => @kubernetes_url
92
- }
93
- kubernetes_metadata['annotations'] = annotations unless annotations.empty?
94
- return kubernetes_metadata
95
- end
96
-
97
- def parse_namespace_metadata(namespace_object)
98
- labels = syms_to_strs(namespace_object['metadata']['labels'].to_h)
99
- annotations = match_annotations(syms_to_strs(namespace_object['metadata']['annotations'].to_h))
100
- if @de_dot
101
- self.de_dot!(labels)
102
- self.de_dot!(annotations)
103
- end
104
- kubernetes_metadata = {
105
- 'namespace_id' => namespace_object['metadata']['uid']
106
- }
107
- kubernetes_metadata['namespace_labels'] = labels unless labels.empty?
108
- kubernetes_metadata['namespace_annotations'] = annotations unless annotations.empty?
109
- return kubernetes_metadata
110
- end
111
-
112
- def get_pod_metadata(namespace_name, pod_name)
72
+ def fetch_pod_metadata(namespace_name, pod_name)
73
+ log.trace("fetching pod metadata: #{namespace_name}/#{pod_name}") if log.trace?
113
74
  begin
114
75
  metadata = @client.get_pod(pod_name, namespace_name)
115
76
  unless metadata
77
+ log.trace("no metadata returned for: #{namespace_name}/#{pod_name}") if log.trace?
116
78
  @stats.bump(:pod_cache_api_nil_not_found)
117
79
  else
118
80
  begin
81
+ log.trace("raw metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
119
82
  metadata = parse_pod_metadata(metadata)
120
83
  @stats.bump(:pod_cache_api_updates)
84
+ log.trace("parsed metadata for #{namespace_name}/#{pod_name}: #{metadata}") if log.trace?
85
+ @cache[metadata['pod_id']] = metadata
121
86
  return metadata
122
87
  rescue Exception=>e
123
88
  log.debug(e)
124
89
  @stats.bump(:pod_cache_api_nil_bad_resp_payload)
125
- nil
90
+ log.trace("returning empty metadata for #{namespace_name}/#{pod_name} due to error") if log.trace?
126
91
  end
127
92
  end
128
93
  rescue KubeException=>e
129
94
  @stats.bump(:pod_cache_api_nil_error)
130
95
  log.debug "Exception encountered fetching pod metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
131
- nil
132
96
  end
97
+ {}
133
98
  end
134
99
 
135
100
  def dump_stats
@@ -139,61 +104,76 @@ module Fluent
139
104
  @stats.set(:pod_cache_size, @cache.count)
140
105
  @stats.set(:namespace_cache_size, @namespace_cache.count) if @namespace_cache
141
106
  log.info(@stats)
107
+ if log.level == Fluent::Log::LEVEL_TRACE
108
+ log.trace(" id cache: #{@id_cache.to_a}")
109
+ log.trace(" pod cache: #{@cache.to_a}")
110
+ log.trace("namespace cache: #{@namespace_cache.to_a}")
111
+ end
142
112
  end
143
113
 
144
- def get_namespace_metadata(namespace_name)
114
+ def fetch_namespace_metadata(namespace_name)
115
+ log.trace("fetching namespace metadata: #{namespace_name}") if log.trace?
145
116
  begin
146
117
  metadata = @client.get_namespace(namespace_name)
147
118
  unless metadata
119
+ log.trace("no metadata returned for: #{namespace_name}") if log.trace?
148
120
  @stats.bump(:namespace_cache_api_nil_not_found)
149
121
  else
150
122
  begin
123
+ log.trace("raw metadata for #{namespace_name}: #{metadata}") if log.trace?
151
124
  metadata = parse_namespace_metadata(metadata)
152
125
  @stats.bump(:namespace_cache_api_updates)
126
+ log.trace("parsed metadata for #{namespace_name}: #{metadata}") if log.trace?
127
+ @namespace_cache[metadata['namespace_id']] = metadata
153
128
  return metadata
154
129
  rescue Exception => e
155
130
  log.debug(e)
156
131
  @stats.bump(:namespace_cache_api_nil_bad_resp_payload)
157
- nil
132
+ log.trace("returning empty metadata for #{namespace_name} due to error") if log.trace?
158
133
  end
159
134
  end
160
135
  rescue KubeException => kube_error
161
136
  @stats.bump(:namespace_cache_api_nil_error)
162
137
  log.debug "Exception encountered fetching namespace metadata from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{kube_error.message}"
163
- nil
164
138
  end
139
+ {}
165
140
  end
166
141
 
167
142
  def initialize
168
143
  super
169
- @stats = KubernetesMetadata::Stats.new
170
144
  @prev_time = Time.now
171
-
172
145
  end
173
146
 
174
147
  def configure(conf)
175
148
  super
176
149
 
150
+ def log.trace?
151
+ level == Fluent::Log::LEVEL_TRACE
152
+ end
153
+
177
154
  require 'kubeclient'
178
155
  require 'active_support/core_ext/object/blank'
179
156
  require 'lru_redux'
157
+ @stats = KubernetesMetadata::Stats.new
180
158
 
181
159
  if @de_dot && (@de_dot_separator =~ /\./).present?
182
160
  raise Fluent::ConfigError, "Invalid de_dot_separator: cannot be or contain '.'"
183
161
  end
184
162
 
185
- if @include_namespace_id
186
- # For compatibility, use include_namespace_metadata instead
187
- @include_namespace_metadata = true
188
- end
189
-
190
163
  if @cache_ttl < 0
164
+ log.info "Setting the cache TTL to :none because it was <= 0"
191
165
  @cache_ttl = :none
192
166
  end
167
+
168
+ # Caches pod/namespace UID tuples for a given container UID.
169
+ @id_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
170
+
171
+ # Use the container UID as the key to fetch a hash containing pod metadata
193
172
  @cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
194
- if @include_namespace_metadata
195
- @namespace_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
196
- end
173
+
174
+ # Use the namespace UID as the key to fetch a hash containing namespace metadata
175
+ @namespace_cache = LruRedux::TTL::ThreadSafeCache.new(@cache_size, @cache_ttl)
176
+
197
177
  @tag_to_kubernetes_name_regexp_compiled = Regexp.compile(@tag_to_kubernetes_name_regexp)
198
178
  @container_name_to_kubernetes_regexp_compiled = Regexp.compile(@container_name_to_kubernetes_regexp)
199
179
 
@@ -247,12 +227,10 @@ module Fluent
247
227
  end
248
228
 
249
229
  if @watch
250
- thread = Thread.new(self) { |this| this.start_watch }
230
+ thread = Thread.new(self) { |this| this.start_pod_watch }
251
231
  thread.abort_on_exception = true
252
- if @include_namespace_metadata
253
- namespace_thread = Thread.new(self) { |this| this.start_namespace_watch }
254
- namespace_thread.abort_on_exception = true
255
- end
232
+ namespace_thread = Thread.new(self) { |this| this.start_namespace_watch }
233
+ namespace_thread.abort_on_exception = true
256
234
  end
257
235
  end
258
236
  if @use_journal
@@ -274,35 +252,74 @@ module Fluent
274
252
 
275
253
  end
276
254
 
277
- def get_metadata_for_record(namespace_name, pod_name, container_name)
255
+ def get_metadata_for_record(match_data, cache_key, create_time)
256
+ namespace_name = match_data['namespace']
257
+ pod_name = match_data['pod_name']
278
258
  metadata = {
279
- 'container_name' => container_name,
259
+ 'container_name' => match_data['container_name'],
280
260
  'namespace_name' => namespace_name,
281
- 'pod_name' => pod_name,
261
+ 'pod_name' => pod_name
282
262
  }
283
263
  if @kubernetes_url.present?
284
- cache_key = "#{namespace_name}_#{pod_name}"
285
-
286
- this = self
287
- pod_metadata = @cache.getset(cache_key) {
288
- @stats.bump(:pod_cache_miss)
289
- md = this.get_pod_metadata(
290
- namespace_name,
291
- pod_name,
292
- )
293
- md
294
- }
264
+ pod_metadata = get_pod_metadata(cache_key, namespace_name, pod_name, create_time)
295
265
  metadata.merge!(pod_metadata) if pod_metadata
266
+ end
267
+ metadata
268
+ end
269
+
270
+ def get_pod_metadata(key, namespace_name, pod_name, record_create_time)
271
+ ids = @id_cache.fetch(key) do
272
+ #no metadata found
273
+ @stats.bump(:id_cache_miss)
274
+ pod_metadata = fetch_pod_metadata(namespace_name, pod_name)
275
+ namespace_metadata = fetch_namespace_metadata(namespace_name)
276
+ ids = {:pod_id=> pod_metadata['pod_id'], :namespace_id => namespace_metadata['namespace_id'] }
277
+ # pod not found or namespace not found
278
+ if ids[:pod_id].nil? || ids[:namespace_id].nil?
279
+ # pod notfound & namespace found
280
+ if ids[:pod_id].nil? && !ids[:namespace_id].nil?
281
+ ns_time = Time.parse(namespace_metadata['creation_timestamp'])
282
+ ids[:pod_id] = key if ns_time <= record_create_time #ns is older then record
283
+ ids
284
+ else
285
+ # nothing found
286
+ @stats.bump(:pod_cache_orphaned_record)
287
+ ids = :orphaned
288
+ end
289
+ end
290
+ ids
291
+ end
296
292
 
297
- if @include_namespace_metadata
298
- namespace_metadata = @namespace_cache.getset(namespace_name) {
299
- @stats.bump(:namespace_cache_miss)
300
- get_namespace_metadata(namespace_name)
293
+ if ids == :orphaned
294
+ return {} unless @allow_orphans
295
+ log.trace("orphaning message for: #{namespace_name}/#{pod_name} ") if log.trace?
296
+ return {
297
+ 'orphaned_namespace' => namespace_name,
298
+ 'namespace_name' => @orphaned_namespace_name,
299
+ 'namespace_id' => @orphaned_namespace_id
301
300
  }
302
- metadata.merge!(namespace_metadata) if namespace_metadata
303
301
  end
304
- end
305
- metadata
302
+ metadata = @cache.fetch(ids[:pod_id]) do
303
+ @stats.bump(:pod_cache_miss)
304
+ m = fetch_pod_metadata(namespace_name, pod_name)
305
+ (m.nil? || m.empty?) ? {'pod_id'=>ids[:pod_id]} : m
306
+ end
307
+ metadata.merge!(@namespace_cache.fetch(ids[:namespace_id]) do
308
+ @stats.bump(:namespace_cache_miss)
309
+ m = fetch_namespace_metadata(namespace_name)
310
+ (m.nil? || m.empty?) ? {'namespace_id'=>ids[:namespace_id]} : m
311
+ end)
312
+ metadata.delete('creation_timestamp') #remove namespace info thats only used for comparison
313
+ metadata
314
+ end
315
+
316
+ def create_time_from_record(record)
317
+ time = if @use_journal
318
+ record['_SOURCE_REALTIME_TIMESTAMP'].nil? ? record['_SOURCE_REALTIME_TIMESTAMP'] : record['__REALTIME_TIMESTAMP']
319
+ else
320
+ record['time']
321
+ end
322
+ (time.nil? || time.chop.empty?) ? Time.now : Time.parse(time)
306
323
  end
307
324
 
308
325
  def filter_stream(tag, es)
@@ -315,15 +332,12 @@ module Fluent
315
332
  match_data = tag.match(@tag_to_kubernetes_name_regexp_compiled)
316
333
 
317
334
  if match_data
335
+ container_id = match_data['docker_id']
318
336
  metadata = {
319
337
  'docker' => {
320
- 'container_id' => match_data['docker_id']
338
+ 'container_id' => container_id
321
339
  },
322
- 'kubernetes' => get_metadata_for_record(
323
- match_data['namespace'],
324
- match_data['pod_name'],
325
- match_data['container_name'],
326
- ),
340
+ 'kubernetes' => get_metadata_for_record(match_data, container_id, create_time_from_record(es.first[1]))
327
341
  }
328
342
  end
329
343
 
@@ -346,15 +360,12 @@ module Fluent
346
360
  metadata = nil
347
361
  if record.has_key?('CONTAINER_NAME') && record.has_key?('CONTAINER_ID_FULL')
348
362
  metadata = record['CONTAINER_NAME'].match(@container_name_to_kubernetes_regexp_compiled) do |match_data|
363
+ container_id = record['CONTAINER_ID_FULL']
349
364
  metadata = {
350
365
  'docker' => {
351
- 'container_id' => record['CONTAINER_ID_FULL']
366
+ 'container_id' => container_id
352
367
  },
353
- 'kubernetes' => get_metadata_for_record(
354
- match_data['namespace'],
355
- match_data['pod_name'],
356
- match_data['container_name'],
357
- )
368
+ 'kubernetes' => get_metadata_for_record(match_data, container_id, create_time_from_record(record))
358
369
  }
359
370
 
360
371
  metadata
@@ -381,10 +392,10 @@ module Fluent
381
392
 
382
393
  def merge_json_log(record)
383
394
  if record.has_key?(@merge_json_log_key)
384
- log = record[@merge_json_log_key].strip
385
- if log[0].eql?('{') && log[-1].eql?('}')
395
+ value = record[@merge_json_log_key].strip
396
+ if value[0].eql?('{') && value[-1].eql?('}')
386
397
  begin
387
- record = JSON.parse(log).merge(record)
398
+ record = JSON.parse(value).merge(record)
388
399
  unless @preserve_json_log
389
400
  record.delete(@merge_json_log_key)
390
401
  end
@@ -407,82 +418,5 @@ module Fluent
407
418
  end
408
419
  end
409
420
 
410
- def match_annotations(annotations)
411
- result = {}
412
- @annotations_regexps.each do |regexp|
413
- annotations.each do |key, value|
414
- if ::Fluent::StringUtil.match_regexp(regexp, key.to_s)
415
- result[key] = value
416
- end
417
- end
418
- end
419
- result
420
- end
421
-
422
- def start_watch
423
- begin
424
- resource_version = @client.get_pods.resourceVersion
425
- watcher = @client.watch_pods(resource_version)
426
- rescue Exception => e
427
- message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
428
- message += " (#{e.response})" if e.respond_to?(:response)
429
-
430
- raise Fluent::ConfigError, message
431
- end
432
-
433
- watcher.each do |notice|
434
- case notice.type
435
- when 'MODIFIED'
436
- cache_key = "#{notice.object['metadata']['namespace']}_#{notice.object['metadata']['name']}"
437
- cached = @cache[cache_key]
438
- if cached
439
- @cache[cache_key] = parse_pod_metadata(notice.object)
440
- @stats.bump(:pod_cache_watch_updates)
441
- else
442
- @stats.bump(:pod_cache_watch_misses)
443
- end
444
- when 'DELETED'
445
- cache_key = "#{notice.object['metadata']['namespace']}_#{notice.object['metadata']['name']}"
446
- @cache.delete(cache_key)
447
- @stats.bump(:pod_cache_watch_deletes)
448
- else
449
- # Don't pay attention to creations, since the created pod may not
450
- # end up on this node.
451
- @stats.bump(:pod_cache_watch_ignored)
452
- end
453
- end
454
- end
455
-
456
- def start_namespace_watch
457
- begin
458
- resource_version = @client.get_namespaces.resourceVersion
459
- watcher = @client.watch_namespaces(resource_version)
460
- rescue Exception=>e
461
- message = "start_namespace_watch: Exception encountered setting up namespace watch from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
462
- message += " (#{e.response})" if e.respond_to?(:response)
463
- log.debug(message)
464
- raise Fluent::ConfigError, message
465
- end
466
- watcher.each do |notice|
467
- case notice.type
468
- when 'MODIFIED'
469
- cache_key = notice.object['metadata']['name']
470
- cached = @namespace_cache[cache_key]
471
- if cached
472
- @namespace_cache[cache_key] = parse_namespace_metadata(notice.object)
473
- @stats.bump(:namespace_cache_watch_updates)
474
- else
475
- @stats.bump(:namespace_cache_watch_misses)
476
- end
477
- when 'DELETED'
478
- @namespace_cache.delete(notice.object['metadata']['name'])
479
- @stats.bump(:namespace_cache_watch_deletes)
480
- else
481
- # Don't pay attention to creations, since the created namespace may not
482
- # be used by any pod on this node.
483
- @stats.bump(:namespace_cache_watch_ignored)
484
- end
485
- end
486
- end
487
421
  end
488
422
  end
@@ -0,0 +1,85 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2017 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ module KubernetesMetadata
20
+ module Common
21
+
22
+ def match_annotations(annotations)
23
+ result = {}
24
+ @annotations_regexps.each do |regexp|
25
+ annotations.each do |key, value|
26
+ if ::Fluent::StringUtil.match_regexp(regexp, key.to_s)
27
+ result[key] = value
28
+ end
29
+ end
30
+ end
31
+ result
32
+ end
33
+
34
+ def parse_namespace_metadata(namespace_object)
35
+ labels = syms_to_strs(namespace_object['metadata']['labels'].to_h)
36
+ annotations = match_annotations(syms_to_strs(namespace_object['metadata']['annotations'].to_h))
37
+ if @de_dot
38
+ self.de_dot!(labels)
39
+ self.de_dot!(annotations)
40
+ end
41
+ kubernetes_metadata = {
42
+ 'namespace_id' => namespace_object['metadata']['uid'],
43
+ 'creation_timestamp' => namespace_object['metadata']['creationTimestamp']
44
+ }
45
+ kubernetes_metadata['namespace_labels'] = labels unless labels.empty?
46
+ kubernetes_metadata['namespace_annotations'] = annotations unless annotations.empty?
47
+ return kubernetes_metadata
48
+ end
49
+
50
+ def parse_pod_metadata(pod_object)
51
+ labels = syms_to_strs(pod_object['metadata']['labels'].to_h)
52
+ annotations = match_annotations(syms_to_strs(pod_object['metadata']['annotations'].to_h))
53
+ if @de_dot
54
+ self.de_dot!(labels)
55
+ self.de_dot!(annotations)
56
+ end
57
+ kubernetes_metadata = {
58
+ 'namespace_name' => pod_object['metadata']['namespace'],
59
+ 'pod_id' => pod_object['metadata']['uid'],
60
+ 'pod_name' => pod_object['metadata']['name'],
61
+ 'labels' => labels,
62
+ 'host' => pod_object['spec']['nodeName'],
63
+ 'master_url' => @kubernetes_url
64
+ }
65
+ kubernetes_metadata['annotations'] = annotations unless annotations.empty?
66
+ return kubernetes_metadata
67
+ end
68
+
69
+ def syms_to_strs(hsh)
70
+ newhsh = {}
71
+ hsh.each_pair do |kk,vv|
72
+ if vv.is_a?(Hash)
73
+ vv = syms_to_strs(vv)
74
+ end
75
+ if kk.is_a?(Symbol)
76
+ newhsh[kk.to_s] = vv
77
+ else
78
+ newhsh[kk] = vv
79
+ end
80
+ end
81
+ newhsh
82
+ end
83
+
84
+ end
85
+ end
@@ -2,7 +2,7 @@
2
2
  # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
3
  # Kubernetes metadata
4
4
  #
5
- # Copyright 2015 Red Hat, Inc.
5
+ # Copyright 2017 Red Hat, Inc.
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ module KubernetesMetadata
21
21
  class Stats
22
22
 
23
23
  def initialize
24
- @stats = LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
24
+ @stats = ::LruRedux::TTL::ThreadSafeCache.new(1000, 3600)
25
25
  end
26
26
 
27
27
  def bump(key)
@@ -32,6 +32,10 @@ module KubernetesMetadata
32
32
  @stats[key] = value
33
33
  end
34
34
 
35
+ def [](key)
36
+ @stats[key]
37
+ end
38
+
35
39
  def to_s
36
40
  "stats - " + [].tap do |a|
37
41
  @stats.each {|k,v| a << "#{k.to_s}: #{v}"}
@@ -0,0 +1,60 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2017 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require_relative 'kubernetes_metadata_common'
20
+
21
+ module KubernetesMetadata
22
+ module WatchNamespaces
23
+
24
+ include ::KubernetesMetadata::Common
25
+
26
+ def start_namespace_watch
27
+ begin
28
+ resource_version = @client.get_namespaces.resourceVersion
29
+ watcher = @client.watch_namespaces(resource_version)
30
+ rescue Exception=>e
31
+ message = "start_namespace_watch: Exception encountered setting up namespace watch from Kubernetes API #{@apiVersion} endpoint #{@kubernetes_url}: #{e.message}"
32
+ message += " (#{e.response})" if e.respond_to?(:response)
33
+ log.debug(message)
34
+ raise Fluent::ConfigError, message
35
+ end
36
+ watcher.each do |notice|
37
+ case notice.type
38
+ when 'MODIFIED'
39
+ cache_key = notice.object['metadata']['uid']
40
+ cached = @namespace_cache[cache_key]
41
+ if cached
42
+ @namespace_cache[cache_key] = parse_namespace_metadata(notice.object)
43
+ @stats.bump(:namespace_cache_watch_updates)
44
+ else
45
+ @stats.bump(:namespace_cache_watch_misses)
46
+ end
47
+ when 'DELETED'
48
+ # ignore and let age out for cases where
49
+ # deleted but still processing logs
50
+ @stats.bump(:namespace_cache_watch_deletes_ignored)
51
+ else
52
+ # Don't pay attention to creations, since the created namespace may not
53
+ # be used by any pod on this node.
54
+ @stats.bump(:namespace_cache_watch_ignored)
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2017 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require_relative 'kubernetes_metadata_common'
20
+
21
+ module KubernetesMetadata
22
+ module WatchPods
23
+
24
+ include ::KubernetesMetadata::Common
25
+
26
+ def start_pod_watch
27
+ begin
28
+ resource_version = @client.get_pods.resourceVersion
29
+ watcher = @client.watch_pods(resource_version)
30
+ rescue Exception => e
31
+ message = "Exception encountered fetching metadata from Kubernetes API endpoint: #{e.message}"
32
+ message += " (#{e.response})" if e.respond_to?(:response)
33
+
34
+ raise Fluent::ConfigError, message
35
+ end
36
+
37
+ watcher.each do |notice|
38
+ case notice.type
39
+ when 'MODIFIED'
40
+ cache_key = notice.object['metadata']['uid']
41
+ cached = @cache[cache_key]
42
+ if cached
43
+ @cache[cache_key] = parse_pod_metadata(notice.object)
44
+ @stats.bump(:pod_cache_watch_updates)
45
+ else
46
+ @stats.bump(:pod_cache_watch_misses)
47
+ end
48
+ when 'DELETED'
49
+ # ignore and let age out for cases where pods
50
+ # deleted but still processing logs
51
+ @stats.bump(:pod_cache_watch_delete_ignored)
52
+ else
53
+ # Don't pay attention to creations, since the created pod may not
54
+ # end up on this node.
55
+ @stats.bump(:pod_cache_watch_ignored)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -30,6 +30,7 @@ require 'test/unit/rr'
30
30
  require 'fileutils'
31
31
  require 'fluent/log'
32
32
  require 'fluent/test'
33
+ require 'minitest/autorun'
33
34
  require 'webmock/test_unit'
34
35
  require 'vcr'
35
36
 
@@ -151,8 +151,15 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
151
151
  kubernetes_url https://localhost:8443
152
152
  watch false
153
153
  cache_size 1
154
- ')
155
- d = create_driver(config)
154
+ include_namespace_metadata true
155
+ ', d: nil)
156
+ d = create_driver(config) if d.nil?
157
+ if ENV['LOGLEVEL']
158
+ logger = Logger.new(STDOUT)
159
+ logger.level = eval("Logger::#{ENV['LOGLEVEL'].upcase}")
160
+ instance = d.instance
161
+ instance.instance_variable_set(:@log,logger)
162
+ end
156
163
  d.run {
157
164
  d.emit(msg, @time)
158
165
  }.filtered
@@ -169,10 +176,82 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
169
176
  }.filtered
170
177
  end
171
178
 
179
+ test 'with docker & kubernetes metadata where id cache hit and metadata miss' do
180
+ VCR.use_cassette('kubernetes_docker_metadata') do
181
+ driver = create_driver('
182
+ kubernetes_url https://localhost:8443
183
+ watch false
184
+ cache_size 1
185
+ include_namespace_metadata true
186
+ ')
187
+ cache = driver.instance.instance_variable_get(:@id_cache)
188
+ cache['49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'] = {
189
+ :pod_id =>'c76927af-f563-11e4-b32d-54ee7527188d',
190
+ :namespace_id =>'898268c8-4a36-11e5-9d81-42010af0194c'
191
+ }
192
+ stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc').to_timeout
193
+ stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default').to_timeout
194
+ es = emit({'time'=>'2015-05-08T09:22:01Z'}, '', d:driver)
195
+ expected_kube_metadata = {
196
+ 'time'=>'2015-05-08T09:22:01Z',
197
+ 'docker' => {
198
+ 'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
199
+ },
200
+ 'kubernetes' => {
201
+ 'pod_name' => 'fabric8-console-controller-98rqc',
202
+ 'container_name' => 'fabric8-console-container',
203
+ 'namespace_name' => 'default',
204
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
205
+ 'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
206
+ }
207
+ }
208
+
209
+ assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
210
+ end
211
+ end
212
+
213
+ test 'with docker & kubernetes metadata where id cache hit and metadata is reloaded' do
214
+ VCR.use_cassette('kubernetes_docker_metadata') do
215
+ driver = create_driver('
216
+ kubernetes_url https://localhost:8443
217
+ watch false
218
+ cache_size 1
219
+ include_namespace_metadata true
220
+ ')
221
+ cache = driver.instance.instance_variable_get(:@id_cache)
222
+ cache['49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'] = {
223
+ :pod_id =>'c76927af-f563-11e4-b32d-54ee7527188d',
224
+ :namespace_id =>'898268c8-4a36-11e5-9d81-42010af0194c'
225
+ }
226
+ es = emit({'time'=>'2015-05-08T09:22:01Z'}, '', d:driver)
227
+ expected_kube_metadata = {
228
+ 'time'=>'2015-05-08T09:22:01Z',
229
+ 'docker' => {
230
+ 'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
231
+ },
232
+ 'kubernetes' => {
233
+ 'host' => 'jimmi-redhat.localnet',
234
+ 'pod_name' => 'fabric8-console-controller-98rqc',
235
+ 'container_name' => 'fabric8-console-container',
236
+ 'namespace_name' => 'default',
237
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
238
+ 'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
239
+ 'master_url' => 'https://localhost:8443',
240
+ 'labels' => {
241
+ 'component' => 'fabric8Console'
242
+ }
243
+ }
244
+ }
245
+
246
+ assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
247
+ end
248
+ end
249
+
172
250
  test 'with docker & kubernetes metadata' do
173
251
  VCR.use_cassette('kubernetes_docker_metadata') do
174
- es = emit()
252
+ es = emit({'time'=>'2015-05-08T09:22:01Z'})
175
253
  expected_kube_metadata = {
254
+ 'time'=>'2015-05-08T09:22:01Z',
176
255
  'docker' => {
177
256
  'container_id' => '49095a2894da899d3b327c5fde1e056a81376cc9a8f8b09a195f2a92bceed459'
178
257
  },
@@ -181,6 +260,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
181
260
  'pod_name' => 'fabric8-console-controller-98rqc',
182
261
  'container_name' => 'fabric8-console-container',
183
262
  'namespace_name' => 'default',
263
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
184
264
  'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
185
265
  'master_url' => 'https://localhost:8443',
186
266
  'labels' => {
@@ -188,6 +268,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
188
268
  }
189
269
  }
190
270
  }
271
+
191
272
  assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
192
273
  end
193
274
  end
@@ -238,6 +319,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
238
319
  'pod_name' => 'fabric8-console-controller-98rqc',
239
320
  'container_name' => 'fabric8-console-container',
240
321
  'namespace_name' => 'default',
322
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
241
323
  'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
242
324
  'master_url' => 'https://localhost:8443',
243
325
  'labels' => {
@@ -271,6 +353,7 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
271
353
  }.to_json
272
354
  )
273
355
  stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default/pods/fabric8-console-controller-98rqc').to_timeout
356
+ stub_request(:any, 'https://localhost:8443/api/v1/namespaces/default').to_timeout
274
357
  es = emit()
275
358
  expected_kube_metadata = {
276
359
  'docker' => {
@@ -279,7 +362,9 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
279
362
  'kubernetes' => {
280
363
  'pod_name' => 'fabric8-console-controller-98rqc',
281
364
  'container_name' => 'fabric8-console-container',
282
- 'namespace_name' => 'default'
365
+ 'namespace_name' => '.orphaned',
366
+ 'orphaned_namespace' => 'default',
367
+ 'namespace_id' => 'orphaned'
283
368
  }
284
369
  }
285
370
  assert_equal(expected_kube_metadata, es.instance_variable_get(:@record_array)[0])
@@ -350,6 +435,15 @@ class KubernetesMetadataFilterTest < Test::Unit::TestCase
350
435
  assert_equal(msg.merge(json_log), es.instance_variable_get(:@record_array)[0])
351
436
  end
352
437
 
438
+ test 'ignores invalid json in log field' do
439
+ json_log = "{'foo':123}"
440
+ msg = {
441
+ 'log' => json_log
442
+ }
443
+ es = emit_with_tag('non-kubernetes', msg, '')
444
+ assert_equal(msg, es.instance_variable_get(:@record_array)[0])
445
+ end
446
+
353
447
  test 'merges json log data with message field in MESSAGE' do
354
448
  json_log = {
355
449
  'timeMillis' => 1459853347608,
@@ -491,6 +585,7 @@ use_journal true
491
585
  'pod_name' => 'fabric8-console-controller-98rqc',
492
586
  'container_name' => 'fabric8-console-container',
493
587
  'namespace_name' => 'default',
588
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
494
589
  'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
495
590
  'master_url' => 'https://localhost:8443',
496
591
  'labels' => {
@@ -556,6 +651,7 @@ use_journal true
556
651
  'pod_name' => 'fabric8-console-controller-98rqc',
557
652
  'container_name' => 'fabric8-console-container',
558
653
  'namespace_name' => 'default',
654
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
559
655
  'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
560
656
  'master_url' => 'https://localhost:8443',
561
657
  'labels' => {
@@ -595,6 +691,7 @@ use_journal true
595
691
  'pod_name' => 'fabric8-console-controller-98rqc',
596
692
  'container_name' => 'fabric8-console-container',
597
693
  'namespace_name' => 'default',
694
+ 'namespace_id' => '898268c8-4a36-11e5-9d81-42010af0194c',
598
695
  'pod_id' => 'c76927af-f563-11e4-b32d-54ee7527188d',
599
696
  'master_url' => 'https://localhost:8443',
600
697
  'labels' => {
@@ -0,0 +1,91 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2015 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require_relative '../helper'
20
+ require 'ostruct'
21
+ require_relative 'watch_test'
22
+
23
+ class WatchNamespacesTestTest < WatchTest
24
+
25
+ include KubernetesMetadata::WatchNamespaces
26
+
27
+ setup do
28
+ @created = OpenStruct.new(
29
+ type: 'CREATED',
30
+ object: {
31
+ 'metadata' => {
32
+ 'name' => 'created',
33
+ 'uid' => 'created_uid'
34
+ }
35
+ }
36
+ )
37
+ @modified = OpenStruct.new(
38
+ type: 'MODIFIED',
39
+ object: {
40
+ 'metadata' => {
41
+ 'name' => 'foo',
42
+ 'uid' => 'modified_uid'
43
+ }
44
+ }
45
+ )
46
+ @deleted = OpenStruct.new(
47
+ type: 'DELETED',
48
+ object: {
49
+ 'metadata' => {
50
+ 'name' => 'deleteme',
51
+ 'uid' => 'deleted_uid'
52
+ }
53
+ }
54
+ )
55
+ end
56
+
57
+ test 'namespace watch ignores CREATED' do
58
+ @client.stub :watch_namespaces, [@created] do
59
+ start_namespace_watch
60
+ assert_equal(false, @namespace_cache.key?('created_uid'))
61
+ assert_equal(1, @stats[:namespace_cache_watch_ignored])
62
+ end
63
+ end
64
+
65
+ test 'namespace watch ignores MODIFIED when info not in cache' do
66
+ @client.stub :watch_namespaces, [@modified] do
67
+ start_namespace_watch
68
+ assert_equal(false, @namespace_cache.key?('modified_uid'))
69
+ assert_equal(1, @stats[:namespace_cache_watch_misses])
70
+ end
71
+ end
72
+
73
+ test 'namespace watch updates cache when MODIFIED is received and info is cached' do
74
+ @namespace_cache['modified_uid'] = {}
75
+ @client.stub :watch_namespaces, [@modified] do
76
+ start_namespace_watch
77
+ assert_equal(true, @namespace_cache.key?('modified_uid'))
78
+ assert_equal(1, @stats[:namespace_cache_watch_updates])
79
+ end
80
+ end
81
+
82
+ test 'namespace watch ignores DELETED' do
83
+ @namespace_cache['deleted_uid'] = {}
84
+ @client.stub :watch_namespaces, [@deleted] do
85
+ start_namespace_watch
86
+ assert_equal(true, @namespace_cache.key?('deleted_uid'))
87
+ assert_equal(1, @stats[:namespace_cache_watch_deletes_ignored])
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,102 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2015 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require_relative '../helper'
20
+ require 'ostruct'
21
+ require_relative 'watch_test'
22
+
23
+ class DefaultPodWatchStrategyTest < WatchTest
24
+
25
+ include KubernetesMetadata::WatchPods
26
+
27
+ setup do
28
+ @created = OpenStruct.new(
29
+ type: 'CREATED',
30
+ object: {
31
+ 'metadata' => {
32
+ 'name' => 'created',
33
+ 'namespace' => 'create',
34
+ 'uid' => 'created_uid',
35
+ 'labels' => {},
36
+ },
37
+ 'spec' => {
38
+ 'nodeName' => 'aNodeName'
39
+ }
40
+ }
41
+ )
42
+ @modified = OpenStruct.new(
43
+ type: 'MODIFIED',
44
+ object: {
45
+ 'metadata' => {
46
+ 'name' => 'foo',
47
+ 'namespace' => 'modified',
48
+ 'uid' => 'modified_uid',
49
+ 'labels' => {},
50
+ },
51
+ 'spec' => {
52
+ 'nodeName' => 'aNodeName'
53
+ }
54
+ }
55
+ )
56
+ @deleted = OpenStruct.new(
57
+ type: 'DELETED',
58
+ object: {
59
+ 'metadata' => {
60
+ 'name' => 'deleteme',
61
+ 'namespace' => 'deleted',
62
+ 'uid' => 'deleted_uid'
63
+ }
64
+ }
65
+ )
66
+ end
67
+
68
+ test 'pod watch notice ignores CREATED' do
69
+ @client.stub :watch_pods, [@created] do
70
+ start_pod_watch
71
+ assert_equal(false, @cache.key?('created_uid'))
72
+ assert_equal(1, @stats[:pod_cache_watch_ignored])
73
+ end
74
+ end
75
+
76
+ test 'pod watch notice is ignored when info not cached and MODIFIED is received' do
77
+ @client.stub :watch_pods, [@modified] do
78
+ start_pod_watch
79
+ assert_equal(false, @cache.key?('modified_uid'))
80
+ assert_equal(1, @stats[:pod_cache_watch_misses])
81
+ end
82
+ end
83
+
84
+ test 'pod watch notice is updated when MODIFIED is received' do
85
+ @cache['modified_uid'] = {}
86
+ @client.stub :watch_pods, [@modified] do
87
+ start_pod_watch
88
+ assert_equal(true, @cache.key?('modified_uid'))
89
+ assert_equal(1, @stats[:pod_cache_watch_updates])
90
+ end
91
+ end
92
+
93
+ test 'pod watch notice is ignored when delete is received' do
94
+ @cache['deleted_uid'] = {}
95
+ @client.stub :watch_pods, [@deleted] do
96
+ start_pod_watch
97
+ assert_equal(true, @cache.key?('deleted_uid'))
98
+ assert_equal(1, @stats[:pod_cache_watch_delete_ignored])
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,57 @@
1
+ #
2
+ # Fluentd Kubernetes Metadata Filter Plugin - Enrich Fluentd events with
3
+ # Kubernetes metadata
4
+ #
5
+ # Copyright 2015 Red Hat, Inc.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ require_relative '../helper'
20
+ require 'ostruct'
21
+
22
+ class WatchTest < Test::Unit::TestCase
23
+
24
+ setup do
25
+ @annotations_regexps = []
26
+ @namespace_cache = {}
27
+ @cache = {}
28
+ @stats = KubernetesMetadata::Stats.new
29
+ @client = OpenStruct.new
30
+ def @client.resourceVersion
31
+ '12345'
32
+ end
33
+ def @client.watch_pods(value)
34
+ []
35
+ end
36
+ def @client.watch_namespaces(value)
37
+ []
38
+ end
39
+ def @client.get_namespaces
40
+ self
41
+ end
42
+ def @client.get_pods
43
+ self
44
+ end
45
+ end
46
+
47
+ def watcher=(value)
48
+ end
49
+
50
+ def log
51
+ logger = {}
52
+ def logger.debug(message)
53
+ end
54
+ logger
55
+ end
56
+
57
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-kubernetes_metadata_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.33.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jimmi Dyson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-02 00:00:00.000000000 Z
11
+ date: 2017-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -207,7 +207,10 @@ files:
207
207
  - circle.yml
208
208
  - fluent-plugin-kubernetes_metadata_filter.gemspec
209
209
  - lib/fluent/plugin/filter_kubernetes_metadata.rb
210
+ - lib/fluent/plugin/kubernetes_metadata_common.rb
210
211
  - lib/fluent/plugin/kubernetes_metadata_stats.rb
212
+ - lib/fluent/plugin/kubernetes_metadata_watch_namespaces.rb
213
+ - lib/fluent/plugin/kubernetes_metadata_watch_pods.rb
211
214
  - test/cassettes/invalid_api_server_config.yml
212
215
  - test/cassettes/kubernetes_docker_metadata.yml
213
216
  - test/cassettes/kubernetes_docker_metadata_annotations.yml
@@ -220,9 +223,12 @@ files:
220
223
  - test/plugin/test.token
221
224
  - test/plugin/test_cache_stats.rb
222
225
  - test/plugin/test_filter_kubernetes_metadata.rb
226
+ - test/plugin/test_watch_namespaces.rb
227
+ - test/plugin/test_watch_pods.rb
228
+ - test/plugin/watch_test.rb
223
229
  homepage: https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter
224
230
  licenses:
225
- - ASL2
231
+ - Apache-2.0
226
232
  metadata: {}
227
233
  post_install_message:
228
234
  rdoc_options: []
@@ -243,7 +249,7 @@ rubyforge_project:
243
249
  rubygems_version: 2.6.12
244
250
  signing_key:
245
251
  specification_version: 4
246
- summary: Filter plugin to add Kubernetes metadata
252
+ summary: Fluentd filter plugin to add Kubernetes metadata
247
253
  test_files:
248
254
  - test/cassettes/invalid_api_server_config.yml
249
255
  - test/cassettes/kubernetes_docker_metadata.yml
@@ -257,3 +263,6 @@ test_files:
257
263
  - test/plugin/test.token
258
264
  - test/plugin/test_cache_stats.rb
259
265
  - test/plugin/test_filter_kubernetes_metadata.rb
266
+ - test/plugin/test_watch_namespaces.rb
267
+ - test/plugin/test_watch_pods.rb
268
+ - test/plugin/watch_test.rb