fluent-plugin-kubernetes_metadata_filter 0.33.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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