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 +4 -4
- data/README.md +8 -6
- data/fluent-plugin-label-router.gemspec +2 -1
- data/lib/fluent/plugin/out_label_router.rb +76 -36
- data/test/plugin/test_out_label_router.rb +56 -18
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f98b66b8fcb6667342e2c8e30b1c20eaa07f949eab93514cceba98872502d7f
|
4
|
+
data.tar.gz: 7316dcea967a1dc2160b7996fdd8718d307809e86ab7900f5195b7b7f8e57b3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
78
|
-
|
79
|
-
| labels
|
80
|
-
| namespaces
|
81
|
-
|
|
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
|
-
|
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.
|
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 '
|
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: :
|
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(
|
62
|
+
def initialize(rule, router, registry)
|
59
63
|
@router = router
|
60
|
-
@
|
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?(
|
72
|
-
@
|
73
|
-
if
|
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
|
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(
|
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
|
110
|
+
unless match.namespaces.empty? || match.namespaces.include?(metadata[:namespace])
|
87
111
|
return false
|
88
112
|
end
|
89
|
-
|
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
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
125
|
-
|
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?(
|
160
|
+
if r.match?(input_metadata)
|
129
161
|
orphan_record = false
|
130
162
|
if @sticky_tags
|
131
|
-
@
|
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
|
-
@
|
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
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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]
|
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?({
|
83
|
+
assert_equal(false, r1.match?(labels: { 'app' => 'app2' }, namespace: ''))
|
77
84
|
# Nothing matched: NO GO
|
78
|
-
assert_equal(false, r1.match?({
|
85
|
+
assert_equal(false, r1.match?(labels: { 'app3' => 'app2' }, namespace: ''))
|
79
86
|
|
80
|
-
r2 = Fluent::Plugin::LabelRouterOutput::Route.new(d.instance.routes[1]
|
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?({
|
89
|
+
assert_equal(true, r2.match?(labels: { 'app' => 'app1' }, namespace: 'test'))
|
83
90
|
# Exclude via namespace
|
84
|
-
assert_equal(false, r2.match?({
|
91
|
+
assert_equal(false, r2.match?(labels: { 'app' => 'app2' }, namespace: 'system'))
|
85
92
|
# Nothing matched: NO GO
|
86
|
-
assert_equal(false, r2.match?({
|
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
|
-
|
89
|
-
|
90
|
-
assert_equal(true,
|
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(
|
134
|
-
d.feed(
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
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(
|
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.
|
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:
|
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
|