fluent-plugin-label-router 0.2.0 → 0.2.5

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
  SHA256:
3
- metadata.gz: 07025a5148e5985c287b5d07e4a8f05c642d650734db78c995be11c2ee478a5e
4
- data.tar.gz: 8e787eacb12b8e12a1e2895f919128d1c755e4c553c9e5b218a7970e89f3564b
3
+ metadata.gz: 7f98b66b8fcb6667342e2c8e30b1c20eaa07f949eab93514cceba98872502d7f
4
+ data.tar.gz: 7316dcea967a1dc2160b7996fdd8718d307809e86ab7900f5195b7b7f8e57b3e
5
5
  SHA512:
6
- metadata.gz: 8e116c6ace26a31641f467eed943f5a643c1f4c6737ea602106c43af69f40cac89fe54533b08e596d3c32f9630c5a2d1a9532f37314bdf24081c6b4aeb70a120
7
- data.tar.gz: e8d0c1c3653c047a65fc0642602a7e5e4aef3e3a341653fca64bd7e15323233c31583451591f9b744becd3f54122b55abd0d936ae48d60a63a5fc4dc640695ca
6
+ metadata.gz: c180a282410db01da6f356598cda074390bcfa56e02fa53d8197605228a5b20679f17af766054de9109906829f6bb8efd43d46a968d8f7ad5a05618a0d41c539
7
+ data.tar.gz: 2b74fcc7c70de9fd1cd06ff9f2d070befebc48544962e2eba828c72a131f79358d2f0f877af332f236da18c409cce52425babf24853af706cacbcafc3d68ddf2
data/README.md CHANGED
@@ -74,11 +74,13 @@ Configuration reference
74
74
 
75
75
 
76
76
  #### \<match\>
77
- | Parameter | Description | Type | Default |
78
- |------------|-------------------------------------------------------------------------------|----------|----------|
79
- | labels | Label definition to match record. Example: `app:nginx` | Hash | nil |
80
- | namespaces | Comma separated list of namespaces. Ignored if left empty. | []string | nil |
81
- | negate | Negate the selector meaning to exclude matches | bool | false |
77
+ | Parameter | Description | Type | Default |
78
+ |-----------------|-------------------------------------------------------------------------------|----------|----------|
79
+ | labels | Label definition to match record. Example: `app:nginx` | Hash | nil |
80
+ | namespaces | Comma separated list of namespaces. Ignored if left empty. | []string | nil |
81
+ | hosts | Comma separated list of hosts. Ignored if left empty. | []string | nil |
82
+ | container_names | Comma separated list of container names. Ignored if left empty. | []string | nil |
83
+ | negate | Negate the selector meaning to exclude matches | bool | false |
82
84
 
83
85
  ## Rules of thumb
84
86
 
@@ -190,7 +192,7 @@ Use `default_label` and/or `default_tag` to route non matching records.
190
192
  ```
191
193
  <match example.tag**>
192
194
  @type label_router
193
- default_label @default_sink
195
+ default_route @default_sink
194
196
  <route>
195
197
  ...
196
198
  </route>
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "fluent-plugin-label-router"
6
- spec.version = "0.2.0"
6
+ spec.version = "0.2.5"
7
7
  spec.authors = ["Banzai Cloud"]
8
8
  spec.email = ["info@banzaicloud.com"]
9
9
 
@@ -23,5 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "bundler", "~> 1.14"
24
24
  spec.add_development_dependency "rake", "~> 12.0"
25
25
  spec.add_development_dependency "test-unit", "~> 3.0"
26
+ spec.add_dependency "prometheus-client", ">= 2.1.0"
26
27
  spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
27
28
  end
@@ -11,10 +11,11 @@
11
11
  # distributed under the License is distributed on an "AS IS" BASIS,
12
12
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  # See the License for the specific language governing permissions and
14
- # limitations under the License.
14
+ # limitations under the License
15
15
 
16
16
  require "fluent/plugin/output"
17
- require 'digest/md5'
17
+ require 'prometheus/client'
18
+
18
19
 
19
20
  module Fluent
20
21
  module Plugin
@@ -40,25 +41,40 @@ module Fluent
40
41
  config_param :@label, :string, :default => nil
41
42
  desc "New tag if selectors matched"
42
43
  config_param :tag, :string, :default => ""
44
+ desc "Extra labels for metrics"
45
+ config_param :metrics_labels, :hash, :default => {}
43
46
 
44
- config_section :match, param_name: :selectors, multi: true do
47
+ config_section :match, param_name: :matches, multi: true do
45
48
  desc "Label definition to match record. Example: app:nginx. You can specify more values as comma separated list: key1:value1,key2:value2"
46
49
  config_param :labels, :hash, :default => {}
47
50
  desc "List of namespace definition to filter the record. Ignored if left empty."
48
51
  config_param :namespaces, :array, :default => [], value_type: :string
52
+ desc "List of hosts definition to filter the record. Ignored if left empty."
53
+ config_param :hosts, :array, :default => [], value_type: :string
54
+ desc "List of container names definition to filter the record. Ignored if left empty."
55
+ config_param :container_names, :array, :default => [], value_type: :string
49
56
  desc "Negate the selection making it an exclude"
50
57
  config_param :negate, :bool, :default => false
51
58
  end
52
-
53
59
  end
54
60
 
55
-
56
-
57
61
  class Route
58
- def initialize(selectors, tag, router)
62
+ def initialize(rule, router, registry)
59
63
  @router = router
60
- @selectors = selectors
61
- @tag = tag
64
+ @matches = rule['matches']
65
+ @tag = rule['tag'].to_s
66
+ @label = rule['@label']
67
+ @metrics_labels = (rule['metrics_labels'].map { |k, v| [k.to_sym, v] }.to_h if rule['metrics_labels'])
68
+ @counter = nil
69
+ unless registry.nil?
70
+ @counter = registry.counter(:fluentd_router_records_total, "Total number of events router for the flow")
71
+ end
72
+ end
73
+
74
+ def get_labels
75
+ default = { 'flow': @label }
76
+ labels = default.merge(@metrics_labels)
77
+ labels
62
78
  end
63
79
 
64
80
  # Evaluate selectors
@@ -68,12 +84,12 @@ module Fluent
68
84
  # 3. If match == false and negate == false -> continue
69
85
  # 4. If match == false and negate == true -> continue
70
86
  # There is no match at all -> return false
71
- def match?(labels, namespace)
72
- @selectors.each do |selector|
73
- if (filter_select(selector, labels, namespace) and !selector.negate)
87
+ def match?(metadata)
88
+ @matches.each do |match|
89
+ if filter_select(match, metadata) and !match.negate
74
90
  return true
75
91
  end
76
- if (filter_select(selector, labels, namespace) and selector.negate)
92
+ if filter_select(match, metadata) and match.negate
77
93
  return false
78
94
  end
79
95
  end
@@ -81,12 +97,21 @@ module Fluent
81
97
  end
82
98
 
83
99
  # Returns true if filter passes (filter match)
84
- def filter_select(selector, labels, namespace)
100
+ def filter_select(match, metadata)
101
+ # Break on container_name mismatch
102
+ unless match.hosts.empty? || match.hosts.include?(metadata[:host])
103
+ return false
104
+ end
105
+ # Break on host mismatch
106
+ unless match.container_names.empty? || match.container_names.include?(metadata[:container])
107
+ return false
108
+ end
85
109
  # Break if list of namespaces is not empty and does not include actual namespace
86
- unless selector.namespaces.empty? or selector.namespaces.include?(namespace)
110
+ unless match.namespaces.empty? || match.namespaces.include?(metadata[:namespace])
87
111
  return false
88
112
  end
89
- match_labels(labels, selector.labels)
113
+
114
+ match_labels(metadata[:labels], match.labels)
90
115
  end
91
116
 
92
117
  def emit(tag, time, record)
@@ -103,32 +128,41 @@ module Fluent
103
128
  else
104
129
  @router.emit_stream(@tag, es)
105
130
  end
131
+ # increment the counter for a given label set
132
+ @counter&.increment(by: es.size, labels: get_labels)
106
133
  end
134
+
107
135
  def match_labels(input, match)
108
- return (match.to_a - input.to_a).empty?
136
+ (match.to_a - input.to_a).empty?
109
137
  end
110
138
  end
111
139
 
112
140
  def process(tag, es)
113
141
  if @sticky_tags
114
- if @route_map.has_key?(tag)
115
- # We already matched with this tag send events to the routers
116
- @route_map[tag].each do |r|
117
- r.emit_es(tag, es.dup)
142
+ @mutex.synchronize {
143
+ if @route_map.has_key?(tag)
144
+ # We already matched with this tag send events to the routers
145
+ @route_map[tag].each do |r|
146
+ r.emit_es(tag, es.dup)
147
+ end
148
+ return
118
149
  end
119
- return
120
- end
150
+ }
121
151
  end
122
152
  event_stream = Hash.new {|h, k| h[k] = Fluent::MultiEventStream.new }
123
153
  es.each do |time, record|
124
- input_labels = @access_to_labels.call(record).to_h
125
- input_namespace = @access_to_namespace.call(record).to_s
154
+ input_metadata = { labels: @access_to_labels.call(record).to_h,
155
+ namespace: @access_to_namespace.call(record).to_s,
156
+ container: @access_to_container_name.call(record).to_s,
157
+ host: @access_to_host.call(record).to_s}
126
158
  orphan_record = true
127
159
  @routers.each do |r|
128
- if r.match?(input_labels, input_namespace)
160
+ if r.match?(input_metadata)
129
161
  orphan_record = false
130
162
  if @sticky_tags
131
- @route_map[tag].push(r)
163
+ @mutex.synchronize {
164
+ @route_map[tag].add(r)
165
+ }
132
166
  end
133
167
  if @batch
134
168
  event_stream[r].add(time, record)
@@ -139,7 +173,9 @@ module Fluent
139
173
  end
140
174
  if !@default_router.nil? && orphan_record
141
175
  if @sticky_tags
142
- @route_map[tag].push(@default_router)
176
+ @mutex.synchronize {
177
+ @route_map[tag].add(@default_router)
178
+ }
143
179
  end
144
180
  if @batch
145
181
  event_stream[@default_router].add(time, record)
@@ -147,31 +183,35 @@ module Fluent
147
183
  @default_router.emit(tag, time, record.dup)
148
184
  end
149
185
  end
150
- if @batch
151
- event_stream.each do |r, es|
152
- r.emit_es(tag, es.dup)
153
- end
186
+ end
187
+ if @batch
188
+ event_stream.each do |r, es|
189
+ r.emit_es(tag, es.dup)
154
190
  end
155
191
  end
156
192
  end
157
193
 
158
194
  def configure(conf)
159
195
  super
160
- @route_map = Hash.new { |h, k| h[k] = Array.new }
196
+ @registry = (::Prometheus::Client.registry if @metrics)
197
+ @route_map = Hash.new { |h, k| h[k] = Set.new }
198
+ @mutex = Mutex.new
161
199
  @routers = []
162
200
  @default_router = nil
163
201
  @routes.each do |rule|
164
202
  route_router = event_emitter_router(rule['@label'])
165
- puts rule
166
- @routers << Route.new(rule.selectors, rule.tag.to_s, route_router)
203
+ @routers << Route.new(rule, route_router, @registry)
167
204
  end
168
205
 
169
206
  if @default_route != '' or @default_tag != ''
170
- @default_router = Route.new(nil, @default_tag, event_emitter_router(@default_route))
207
+ default_rule = { 'matches' => nil, 'tag' => @default_tag, '@label' => @default_route}
208
+ @default_router = Route.new(default_rule, event_emitter_router(@default_route), @registry)
171
209
  end
172
210
 
173
211
  @access_to_labels = record_accessor_create("$.kubernetes.labels")
174
212
  @access_to_namespace = record_accessor_create("$.kubernetes.namespace_name")
213
+ @access_to_host = record_accessor_create("$.kubernetes.host")
214
+ @access_to_container_name = record_accessor_create("$.kubernetes.container_name")
175
215
 
176
216
  @batch = @emit_mode == :batch
177
217
  end
@@ -65,29 +65,47 @@ class LabelRouterOutputTest < Test::Unit::TestCase
65
65
  namespaces dev,sandbox
66
66
  </match>
67
67
  </route>
68
+ <route>
69
+ <match>
70
+ labels app:nginx
71
+ namespaces dev,sandbox
72
+ container_names mycontainer
73
+ </match>
74
+ </route>
68
75
  )
69
76
  d = Fluent::Test::Driver::BaseOwner.new(Fluent::Plugin::LabelRouterOutput)
70
77
  d.configure(routing_conf)
71
78
 
72
- r1 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[0].selectors, "",nil)
79
+ r1 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[0], nil,nil)
73
80
  # Selector matched: GO
74
- assert_equal(true, r1.match?({'app' => 'app1'},""))
81
+ assert_equal(true, r1.match?(labels: { 'app' => 'app1' }, namespace: ''))
75
82
  # Exclude match: NO GO
76
- assert_equal(false, r1.match?({"app" => "app2"},""))
83
+ assert_equal(false, r1.match?(labels: { 'app' => 'app2' }, namespace: ''))
77
84
  # Nothing matched: NO GO
78
- assert_equal(false, r1.match?({"app3" => "app2"},""))
85
+ assert_equal(false, r1.match?(labels: { 'app3' => 'app2' }, namespace: ''))
79
86
 
80
- r2 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[1].selectors, "",nil)
87
+ r2 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[1], nil,nil)
81
88
  # Match selector and namespace: GO
82
- assert_equal(true, r2.match?({"app" => "app1"},"test"))
89
+ assert_equal(true, r2.match?(labels: { 'app' => 'app1' }, namespace: 'test'))
83
90
  # Exclude via namespace
84
- assert_equal(false, r2.match?({"app" => "app2"},"system"))
91
+ assert_equal(false, r2.match?(labels: { 'app' => 'app2' }, namespace: 'system'))
85
92
  # Nothing matched: NO GO
86
- assert_equal(false, r2.match?({"app3" => "app"},"system"))
93
+ assert_equal(false, r2.match?(labels: { 'app3' => 'app' }, namespace: 'system'))
94
+
95
+ r3 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[2], nil,nil)
96
+ assert_equal(true, r3.match?(labels: { 'app' => 'nginx' }, namespace: 'dev'))
97
+ assert_equal(true, r3.match?(labels: { 'app' => 'nginx' }, namespace: 'sandbox'))
98
+ assert_equal(false, r3.match?(labels: { 'app' => 'nginx2' }, namespace: 'sandbox'))
87
99
 
88
- r3 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[2].selectors, "",nil)
89
- assert_equal(true, r3.match?({"app" => "nginx"},"dev"))
90
- assert_equal(true, r3.match?({"app" => "nginx"},"sandbox"))
100
+ r4 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[3], nil,nil)
101
+ # Matching container name
102
+ assert_equal(true, r4.match?(labels: { 'app' => 'nginx' }, namespace: 'dev', container: 'mycontainer'))
103
+ # Missing container name is equal to wrong container
104
+ assert_equal(false, r4.match?(labels: { 'app' => 'nginx' }, namespace: 'sandbox'))
105
+ # Wrong container name
106
+ assert_equal(false, r4.match?(labels: { 'app' => 'nginx' }, namespace: 'dev', container: 'mycontainer2'))
107
+ # Wrong label but good namespace and container_name
108
+ assert_equal(false, r4.match?(labels: { 'app' => 'nginx2' }, namespace: 'sandbox', container_name: 'mycontainer2'))
91
109
  end
92
110
  end
93
111
 
@@ -116,6 +134,26 @@ class LabelRouterOutputTest < Test::Unit::TestCase
116
134
  end
117
135
  end
118
136
 
137
+
138
+ sub_test_case 'test_multiple_events_batched' do
139
+ test 'normal' do
140
+ conf = %[
141
+ <route>
142
+ <match>
143
+ </match>
144
+ </route>
145
+ ]
146
+ event_time = event_time("2019-07-17 11:11:11 UTC")
147
+ d = create_driver(conf)
148
+ d.run(expect_emits: 1, expect_records: 2) do
149
+ d.feed("test", [
150
+ [event_time, {"kubernetes" => {} } ],
151
+ [event_time, {"kubernetes" => {} } ],
152
+ ])
153
+ end
154
+ end
155
+ end
156
+
119
157
  sub_test_case 'test_default_router' do
120
158
  test 'normal' do
121
159
  CONFIG2 = %[
@@ -130,11 +168,11 @@ default_tag "new_tag"
130
168
  ]
131
169
  event_time = event_time("2019-07-17 11:11:11 UTC")
132
170
  d = create_driver(CONFIG2)
133
- d.run(default_tag: 'test') do
134
- d.feed(event_time, {"kubernetes" => {"labels" => {"app" => "app1"} } } )
135
- end
136
- d.run(default_tag: 'test2') do
137
- d.feed(event_time, {"kubernetes" => {"labels" => {"app" => "app2"} } } )
171
+ d.run() do
172
+ d.feed("test", [
173
+ [event_time, {"kubernetes" => {"labels" => {"app" => "app1"} } } ],
174
+ [event_time, {"kubernetes" => {"labels" => {"app" => "app2"} } } ],
175
+ ])
138
176
  end
139
177
  events = d.events
140
178
 
@@ -146,7 +184,7 @@ default_tag "new_tag"
146
184
 
147
185
  sub_test_case 'test_empty_router' do
148
186
  test 'normal' do
149
- CONFIG2 = %[
187
+ CONFIG3 = %[
150
188
  <route>
151
189
  tag new_app_tag
152
190
  <match>
@@ -156,7 +194,7 @@ default_tag "new_tag"
156
194
  </route>
157
195
  ]
158
196
  event_time = event_time("2019-07-17 11:11:11 UTC")
159
- d = create_driver(CONFIG2)
197
+ d = create_driver(CONFIG3)
160
198
  d.run(default_tag: 'test') do
161
199
  d.feed(event_time, {"kubernetes" => {"labels" => {"app" => "app1"} } } )
162
200
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-label-router
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Banzai Cloud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2021-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: prometheus-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.1.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.1.0
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: fluentd
57
71
  requirement: !ruby/object:Gem::Requirement