fluent-plugin-mqtt-io 0.2.3 → 0.3.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: c1403811416a6ec5ae4b65f1e2f910425c78464f
4
- data.tar.gz: 36025a84dafc6aacc2295c76cd0a9f8110563514
3
+ metadata.gz: c1ec56bcb964c914765afcd471b69413e623a54d
4
+ data.tar.gz: d7784e5b1e39d416cec4b40a1868ca72fe0f3afc
5
5
  SHA512:
6
- metadata.gz: 64ae726ba5f10ba20d16692626cd9ce1d6424fe1a0aacd735fbacd73f3bf3c927c07838980907e9bb5ef89b3227eb7c03b64b82979de5df3825e168a5a58d5c8
7
- data.tar.gz: 9501641e21422a5fa755550a1e9a32427515e03ef9cfd3721095bbcdb2d6d806e03e36d71b35a1a8d334696a6d7f5e42127145e4bbad970bb89562f4c83ce2a6
6
+ metadata.gz: 62e831aece6900ea1b8f3b8b98fae4cf0391c2bf223803dba5ba8aecd2182c103172523793721ab25bf5279f266e193a3dd4bf86b79167c51b197d44e9ea9475
7
+ data.tar.gz: 3efb8116780288682a990f56c98b4b338a7823e037f9a5088e62071d537b9d2e330841bb06fb68b4adb02ac90ffd93382b1694ef13557cb288326795a3f2c167
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.3.0 (2017-03-20)
2
+
3
+ Features:
4
+ - compatible with fluentd v0.14
5
+ - change license from MIT License to Apache License Version 2.0
6
+
7
+ Deprecations:
8
+
9
+ - deprecated support of fluentd v0.12
data/LICENSE CHANGED
@@ -1,22 +1,191 @@
1
- The MIT License (MIT)
2
1
 
3
- Copyright (c) 2015 Toyokazu Akiyama
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ https://www.apache.org/licenses/
4
5
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
11
7
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
8
+ 1. Definitions.
14
9
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
22
12
 
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ Copyright 2015-2017 Toyokazu Akiyama
180
+
181
+ Licensed under the Apache License, Version 2.0 (the "License");
182
+ you may not use this file except in compliance with the License.
183
+ You may obtain a copy of the License at
184
+
185
+ https://www.apache.org/licenses/LICENSE-2.0
186
+
187
+ Unless required by applicable law or agreed to in writing, software
188
+ distributed under the License is distributed on an "AS IS" BASIS,
189
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
+ See the License for the specific language governing permissions and
191
+ limitations under the License.
data/README.md CHANGED
@@ -240,4 +240,4 @@ MQTT output plugin can be used as the following. If you have tiny computers like
240
240
 
241
241
  ## License
242
242
 
243
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
243
+ The gem is available as open source under the terms of the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.test_files = FileList['test/**/test_*.rb']
8
8
  end
9
9
 
10
10
  task :default => :test
@@ -4,14 +4,14 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "fluent-plugin-mqtt-io"
7
- spec.version = "0.2.3"
7
+ spec.version = "0.3.0"
8
8
  spec.authors = ["Toyokazu Akiyama"]
9
9
  spec.email = ["toyokazu@gmail.com"]
10
10
 
11
11
  spec.summary = %q{fluentd input/output plugin for mqtt broker}
12
12
  spec.description = %q{fluentd input/output plugin for mqtt broker}
13
13
  spec.homepage = "https://github.com/toyokazu/fluent-plugin-mqtt-io"
14
- spec.license = "MIT"
14
+ spec.license = "Apache License Version 2.0"
15
15
 
16
16
  spec.files = `git ls-files`.gsub(/images\/[\w\.]+\n/, "").split($/)
17
17
  spec.bindir = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -19,9 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency 'fluentd', '>= 0.10.0'
23
- spec.add_dependency "mqtt", "~> 0.3"
22
+ spec.required_ruby_version = '>= 2.2.0'
24
23
 
25
- spec.add_development_dependency "bundler", "~> 1.10"
26
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_dependency 'fluentd', '~> 0.14'
25
+ spec.add_dependency "mqtt", "~> 0.4"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.14"
28
+ spec.add_development_dependency "rake", "~> 12.0"
29
+ spec.add_development_dependency "test-unit"
27
30
  end
@@ -1,30 +1,59 @@
1
- module Fluent
1
+ require 'fluent/plugin/input'
2
+ require 'fluent/event'
3
+ require 'fluent/time'
4
+ require 'mqtt'
5
+
6
+ module Fluent::Plugin
2
7
  class MqttInput < Input
3
- Plugin.register_input('mqtt', self)
8
+ Fluent::Plugin.register_input('mqtt', self)
9
+
10
+ helpers :thread, :compat_parameters, :parser
11
+
12
+ MQTT_PORT = 1883
4
13
 
5
- config_param :port, :integer, :default => 1883
14
+ desc 'The address to connect to.'
6
15
  config_param :host, :string, :default => '127.0.0.1'
16
+ desc 'The port to connect to.'
17
+ config_param :port, :integer, :default => MQTT_PORT
18
+ desc 'The topic to subscribe.'
7
19
  config_param :topic, :string, :default => '#'
20
+ desc 'The format to receive.'
8
21
  config_param :format, :string, :default => 'json'
9
- config_param :bulk_trans, :bool, :default => true
10
- config_param :bulk_trans_sep, :string, :default => "\t"
11
- config_param :username, :string, :default => nil
12
- config_param :password, :string, :default => nil
22
+ desc 'Specify keep alive interval.'
13
23
  config_param :keep_alive, :integer, :default => 15
14
- config_param :ssl, :bool, :default => nil
15
- config_param :ca_file, :string, :default => nil
16
- config_param :key_file, :string, :default => nil
17
- config_param :cert_file, :string, :default => nil
18
- config_param :recv_time, :bool, :default => false
19
- config_param :recv_time_key, :string, :default => "recv_time"
24
+ desc 'Specify initial interval for reconnection.'
20
25
  config_param :initial_interval, :integer, :default => 1
26
+ desc 'Specify increasing ratio of reconnection interval.'
21
27
  config_param :retry_inc_ratio, :integer, :default => 2
22
28
 
23
- require 'mqtt'
29
+ # bulk_trans is deprecated
30
+ # multiple entries must be inputted as an Array
31
+ #config_param :bulk_trans, :bool, :default => true
32
+ #config_param :bulk_trans_sep, :string, :default => "\t"
33
+
34
+ config_section :security, required: false, multi: false do
35
+ ### User based authentication
36
+ desc 'The username for authentication'
37
+ config_param :username, :string, :default => nil
38
+ desc 'The password for authentication'
39
+ config_param :password, :string, :default => nil
40
+ desc 'Use TLS or not.'
41
+ config_param :use_tls, :bool, :default => nil
42
+ config_section :tls, required: false, multi: true do
43
+ desc 'Specify TLS ca file.'
44
+ config_param :ca_file, :string, :default => nil
45
+ desc 'Specify TLS key file.'
46
+ config_param :key_file, :string, :default => nil
47
+ desc 'Specify TLS cert file.'
48
+ config_param :cert_file, :string, :default => nil
49
+ end
50
+ end
24
51
 
25
- # Define `router` method of v0.12 to support v0.10 or earlier
26
- unless method_defined?(:router)
27
- define_method("router") { Fluent::Engine }
52
+ config_section :monitor, required: false, multi: false do
53
+ desc 'Record received time into message or not.'
54
+ config_param :recv_time, :bool, :default => false
55
+ desc 'Specify the attribute name of received time.'
56
+ config_param :recv_time_key, :string, :default => "recv_time"
28
57
  end
29
58
 
30
59
  def configure(conf)
@@ -34,10 +63,11 @@ module Fluent
34
63
  end
35
64
 
36
65
  def configure_parser(conf)
37
- @parser = Plugin.new_parser(conf['format'])
38
- @parser.configure(conf)
66
+ compat_parameters_convert(conf, :parser)
67
+ parser_config = conf.elements('parse').first
68
+ @parser = parser_create(conf: parser_config)
39
69
  end
40
-
70
+
41
71
  def init_retry_interval
42
72
  @retry_interval = @initial_interval
43
73
  end
@@ -58,46 +88,51 @@ module Fluent
58
88
  opts = {
59
89
  host: @host,
60
90
  port: @port,
61
- username: @username,
62
- password: @password,
63
91
  keep_alive: @keep_alive
64
92
  }
65
- opts[:ssl] = @ssl if @ssl
66
- opts[:ca_file] = @ca_file if @ca_file
67
- opts[:cert_file] = @cert_file if @cert_file
68
- opts[:key_file] = @key_file if @key_file
93
+ opts[:username] = @security.username if @security.respond_to?(:username)
94
+ opts[:password] = @security.password if @security.respond_to?(:password)
95
+ if @security.respond_to?(:use_tls) && @security.use_tls
96
+ opts[:ssl] = @security.use_tls
97
+ opts[:ca_file] = @security.tls.ca_file
98
+ opts[:cert_file] = @security.tls.cert_file
99
+ opts[:key_file] = @security.tls.key_file
100
+ end
69
101
 
70
102
  # In order to handle Exception raised from reading Thread
71
103
  # in MQTT::Client caused by network disconnection (during read_byte),
72
104
  # @connect_thread generates connection.
73
105
  @client = MQTT::Client.new(opts)
74
- @connect_thread = Thread.new do
75
- while (true)
76
- begin
77
- @client.disconnect if @client.connected?
78
- @client.connect
79
- @client.subscribe(@topic)
80
- @get_thread.kill if !@get_thread.nil? && @get_thread.alive?
81
- @get_thread = Thread.new do
82
- @client.get do |topic, message|
83
- emit(topic, message)
84
- end
106
+ @get_thread = nil
107
+ @connect_thread = Thread.new(&method(:connect_loop))
108
+ end
109
+
110
+ def connect_loop
111
+ while (true)
112
+ begin
113
+ @get_thread.kill if !@get_thread.nil? && @get_thread.alive?
114
+ @client.disconnect if @client.connected?
115
+ @client.connect
116
+ @client.subscribe(@topic)
117
+ @get_thread = Thread.new do
118
+ @client.get do |topic, message|
119
+ emit(topic, message)
85
120
  end
86
- init_retry_interval
87
- sleep
88
- rescue MQTT::ProtocolException => e
89
- sleep_retry_interval(e, "Protocol error occurs.")
90
- next
91
- rescue Timeout::Error => e
92
- sleep_retry_interval(e, "Timeout error occurs.")
93
- next
94
- rescue SystemCallError => e
95
- sleep_retry_interval(e, "System call error occurs.")
96
- next
97
- rescue StandardError=> e
98
- sleep_retry_interval(e, "The other error occurs.")
99
- next
100
121
  end
122
+ init_retry_interval
123
+ sleep
124
+ rescue MQTT::ProtocolException => e
125
+ sleep_retry_interval(e, "Protocol error occurs.")
126
+ next
127
+ rescue Timeout::Error => e
128
+ sleep_retry_interval(e, "Timeout error occurs.")
129
+ next
130
+ rescue SystemCallError => e
131
+ sleep_retry_interval(e, "System call error occurs.")
132
+ next
133
+ rescue StandardError=> e
134
+ sleep_retry_interval(e, "The other error occurs.")
135
+ next
101
136
  end
102
137
  end
103
138
  end
@@ -105,7 +140,7 @@ module Fluent
105
140
  def add_recv_time(record)
106
141
  if @recv_time
107
142
  # recv_time is recorded in ms
108
- record.merge({@recv_time_key => Time.now.instance_eval { self.to_i * 1000 + (usec/1000) }})
143
+ record.merge({@recv_time_key => Fluent::EventTime.now})
109
144
  else
110
145
  record
111
146
  end
@@ -114,8 +149,8 @@ module Fluent
114
149
  def parse(message)
115
150
  @parser.parse(message) do |time, record|
116
151
  if time.nil?
117
- $log.debug "Since time_key field is nil, Fluent::Engine.now is used."
118
- time = Fluent::Engine.now
152
+ $log.debug "Since time_key field is nil, Fluent::EventTime.now is used."
153
+ time = Fluent::EventTime.now
119
154
  end
120
155
  $log.debug "#{topic}, #{time}, #{add_recv_time(record)}"
121
156
  return [time, add_recv_time(record)]
@@ -124,13 +159,16 @@ module Fluent
124
159
 
125
160
  def emit(topic, message)
126
161
  begin
127
- topic.gsub!("/","\.")
128
- if @bulk_trans
129
- message.split(@bulk_trans_sep).each do |m|
130
- router.emit(topic, *parse(m))
162
+ tag = topic.gsub("/","\.")
163
+ time, record = parse(message)
164
+ if record.is_a?(Array)
165
+ mes = Fluent::MultiEventStream.new
166
+ record.each do |single_record|
167
+ mes.add(@parser.parse_time(single_record), single_record)
131
168
  end
169
+ router.emit_stream(tag, mes)
132
170
  else
133
- router.emit(topic, *parse(message))
171
+ router.emit(tag, time, record)
134
172
  end
135
173
  rescue Exception => e
136
174
  $log.error :error => e.to_s
@@ -1,20 +1,225 @@
1
- module Fluent
2
- class MqttOutput < Output
3
- require 'fluent/plugin/mqtt_output_mixin'
4
- include Fluent::MqttOutputMixin
1
+ require 'fluent/plugin/output'
2
+ require 'fluent/event'
3
+ require 'fluent/time'
4
+ require 'mqtt'
5
5
 
6
- # First, register the plugin. NAME is the name of this plugin
7
- # and identifies the plugin in the configuration file.
6
+ module Fluent::Plugin
7
+ class MqttOutput < Output
8
8
  Fluent::Plugin.register_output('mqtt', self)
9
9
 
10
- def emit(tag, es, chain)
11
- es.each {|time,record|
12
- $log.debug "#{tag}, #{format_time(time)}, #{add_send_time(record)}"
13
- @client.publish(rewrite_tag(tag), add_send_time(record).merge(timestamp_hash(time)).to_json)
10
+ helpers :compat_parameters, :formatter, :inject
11
+
12
+ MQTT_PORT = 1883
13
+
14
+ desc 'The address to connect to.'
15
+ config_param :host, :string, :default => '127.0.0.1'
16
+ desc 'The port to connect to.'
17
+ config_param :port, :integer, :default => MQTT_PORT
18
+ desc 'MQTT keep alive interval.'
19
+ config_param :keep_alive, :integer, :default => 15
20
+ desc 'Topic rewrite matching pattern.'
21
+ config_param :topic_rewrite_pattern, :string, :default => nil
22
+ desc 'Topic rewrite replacement string.'
23
+ config_param :topic_rewrite_replacement, :string, :default => nil
24
+ desc 'Initial retry interval.'
25
+ config_param :initial_interval, :integer, :default => 1
26
+ desc 'Increasing ratio of retry interval.'
27
+ config_param :retry_inc_ratio, :integer, :default => 2
28
+
29
+ config_section :security, required: false, multi: false do
30
+ ### User based authentication
31
+ desc 'The username for authentication'
32
+ config_param :username, :string, :default => nil
33
+ desc 'The password for authentication'
34
+ config_param :password, :string, :default => nil
35
+ desc 'Use TLS or not.'
36
+ config_param :use_tls, :bool, :default => nil
37
+ config_section :tls, required: false, multi: true do
38
+ desc 'TLS ca file.'
39
+ config_param :ca_file, :string, :default => nil
40
+ desc 'TLS key file.'
41
+ config_param :key_file, :string, :default => nil
42
+ desc 'TLS cert file.'
43
+ config_param :cert_file, :string, :default => nil
44
+ end
45
+ end
46
+
47
+ config_section :monitor, required: false, multi: false do
48
+ desc 'Recording send time for monitoring.'
49
+ config_param :send_time, :bool, :default => false
50
+ desc 'Recording key name of send time for monitoring.'
51
+ config_param :send_time_key, :string, :default => "send_time"
52
+ end
53
+
54
+ # This method is called before starting.
55
+ # 'conf' is a Hash that includes configuration parameters.
56
+ # If the configuration is invalid, raise Fluent::ConfigError.
57
+ def configure(conf)
58
+ super
59
+ compat_parameters_convert(conf, :formatter, :inject, :buffer, default_chunk_key: "time")
60
+ formatter_config = conf.elements(name: 'format').first
61
+ @formatter = formatter_create(conf: formatter_config)
62
+ @has_buffer_section = conf.elements(name: 'buffer').size > 0
63
+ init_retry_interval
64
+ end
65
+
66
+ def init_retry_interval
67
+ @retry_interval = @initial_interval
68
+ end
69
+
70
+ def increment_retry_interval
71
+ @retry_interval = @retry_interval * @retry_inc_ratio
72
+ end
73
+
74
+ def sleep_retry_interval(e, message)
75
+ $log.error "#{message},#{e.class},#{e.message}"
76
+ $log.error "Retry in #{@retry_interval} sec"
77
+ sleep @retry_interval
78
+ increment_retry_interval
79
+ end
80
+
81
+ def rewrite_tag(tag)
82
+ if @topic_rewrite_pattern.nil?
83
+ tag.gsub("\.", "/")
84
+ else
85
+ tag.gsub("\.", "/").gsub(Regexp.new(@topic_rewrite_pattern), @topic_rewrite_replacement)
86
+ end
87
+ end
88
+
89
+ def prefer_buffered_processing
90
+ @has_buffer_section
91
+ end
92
+
93
+ # This method is called when starting.
94
+ # Open sockets or files here.
95
+ def start
96
+ super
97
+
98
+ $log.debug "start to connect mqtt broker #{@host}:#{@port}"
99
+ opts = {
100
+ host: @host,
101
+ port: @port,
102
+ keep_alive: @keep_alive
14
103
  }
104
+ opts[:username] = @security.username if @security.respond_to?(:username)
105
+ opts[:password] = @security.password if @security.respond_to?(:password)
106
+ if @security.respond_to?(:use_tls) && @security.use_tls
107
+ opts[:ssl] = @security.use_tls
108
+ opts[:ca_file] = @security.tls.ca_file
109
+ opts[:cert_file] = @security.tls.cert_file
110
+ opts[:key_file] = @security.tls.key_file
111
+ end
112
+ # In order to handle Exception raised from reading Thread
113
+ # in MQTT::Client caused by network disconnection (during read_byte),
114
+ # @connect_thread generates connection.
115
+ @client_mutex = Mutex.new
116
+ @client = MQTT::Client.new(opts)
117
+ connect_loop
118
+ end
119
+
120
+ # This method is called when shutting down.
121
+ # Shutdown the thread and close sockets or files here.
122
+ def shutdown
123
+ super
124
+
125
+ @client.disconnect
126
+ end
127
+
128
+ def connect_loop
129
+ while (true)
130
+ @client_mutex.lock
131
+ begin
132
+ @client.disconnect if @client.connected?
133
+ @client.connect
134
+ init_retry_interval
135
+ rescue MQTT::ProtocolException => e
136
+ sleep_retry_interval(e, "Protocol error occurs.")
137
+ @client_mutex.unlock
138
+ next
139
+ rescue Timeout::Error => e
140
+ sleep_retry_interval(e, "Timeout error occurs.")
141
+ @client_mutex.unlock
142
+ next
143
+ rescue SystemCallError => e
144
+ sleep_retry_interval(e, "System call error occurs.")
145
+ @client_mutex.unlock
146
+ next
147
+ rescue StandardError=> e
148
+ sleep_retry_interval(e, "The other error occurs.")
149
+ @client_mutex.unlock
150
+ next
151
+ end
152
+ @client_mutex.unlock
153
+ break
154
+ end
155
+ end
156
+
157
+ def publish_error_handler
158
+ while(true)
159
+ begin
160
+ yield
161
+ rescue MQTT::ProtocolException => e
162
+ sleep_retry_interval(e, "Protocol error occurs.")
163
+ connect_loop
164
+ next
165
+ rescue Timeout::Error => e
166
+ sleep_retry_interval(e, "Timeout error occurs.")
167
+ connect_loop
168
+ next
169
+ rescue SystemCallError => e
170
+ sleep_retry_interval(e, "System call error occurs.")
171
+ connect_loop
172
+ next
173
+ rescue StandardError=> e
174
+ sleep_retry_interval(e, "The other error occurs.")
175
+ connect_loop
176
+ next
177
+ end
178
+ break
179
+ end
180
+ end
181
+
182
+ def add_send_time(record)
183
+ if @send_time
184
+ # send_time is recorded in ms
185
+ record.merge({@send_time_key => Fluent::EventTime.now})
186
+ else
187
+ record
188
+ end
189
+ end
190
+
191
+ def publish_event_stream(tag, es)
192
+ if es.class == Fluent::OneEventStream
193
+ es = inject_values_to_event_stream(tag, es)
194
+ es.each do |time, record|
195
+ $log.debug "#{rewrite_tag(tag)}, #{add_send_time(record)}"
196
+ publish_error_handler do
197
+ @client.publish(rewrite_tag(tag), @formatter.format(tag, time, add_send_time(record)))
198
+ end
199
+ end
200
+ else
201
+ es = inject_values_to_event_stream(tag, es)
202
+ array = []
203
+ es.each do |time, record|
204
+ $log.debug "#{rewrite_tag(tag)}, #{add_send_time(record)}"
205
+ array << add_send_time(record)
206
+ end
207
+ publish_error_handler do
208
+ @client.publish(rewrite_tag(tag), @formatter.format(tag, Fluent::EventTime.now, array))
209
+ end
210
+ end
15
211
  $log.flush
212
+ end
213
+
214
+ def process(tag, es)
215
+ publish_event_stream(tag, es)
216
+ end
217
+
218
+ def write(chunk)
219
+ return if chunk.empty?
220
+ tag = chunk.metadata.tag
16
221
 
17
- chain.next
222
+ publish_event_stream(tag, es)
18
223
  end
19
224
  end
20
225
  end
@@ -0,0 +1,32 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/input'
3
+ require 'fluent/plugin/in_mqtt'
4
+
5
+ class MqttInputTest < Test::Unit::TestCase
6
+ def setup
7
+ Fluent::Test.setup
8
+ end
9
+
10
+
11
+ CONFIG = %[
12
+ ]
13
+
14
+ def create_driver(conf = CONFIG, opts = {})
15
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::MqttInput, opts: opts).configure(conf)
16
+ end
17
+
18
+ sub_test_case "configure" do
19
+ test "host and port" do
20
+ d = create_driver %[
21
+ host 127.0.0.1
22
+ port 1300
23
+ <parse>
24
+ @type json
25
+ time_format %FT%T%:z
26
+ </parse>
27
+ ]
28
+ assert_equal '127.0.0.1', d.instance.host
29
+ assert_equal 1300, d.instance.port
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/output'
3
+ require 'fluent/plugin/out_mqtt'
4
+
5
+ class MqttOutputTest < Test::Unit::TestCase
6
+ def setup
7
+ Fluent::Test.setup
8
+ end
9
+
10
+
11
+ CONFIG = %[
12
+ ]
13
+
14
+ def create_driver(conf = CONFIG, opts = {})
15
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::MqttOutput, opts: opts).configure(conf)
16
+ end
17
+
18
+ sub_test_case 'non-buffered' do
19
+ test 'configure' do
20
+ d = create_driver %[
21
+ host 127.0.0.1
22
+ port 1300
23
+ <format>
24
+ @type json
25
+ </format>
26
+ ]
27
+ assert_equal '127.0.0.1', d.instance.host
28
+ assert_equal 1300, d.instance.port
29
+ end
30
+ end
31
+ end
metadata CHANGED
@@ -1,71 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-mqtt-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Toyokazu Akiyama
8
8
  autorequire:
9
9
  bindir: []
10
10
  cert_chain: []
11
- date: 2016-03-05 00:00:00.000000000 Z
11
+ date: 2017-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.0
19
+ version: '0.14'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.0
26
+ version: '0.14'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: mqtt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
33
+ version: '0.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.3'
40
+ version: '0.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.10'
47
+ version: '1.14'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.10'
54
+ version: '1.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '12.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '12.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: fluentd input/output plugin for mqtt broker
70
84
  email:
71
85
  - toyokazu@gmail.com
@@ -75,19 +89,20 @@ extra_rdoc_files: []
75
89
  files:
76
90
  - ".gitignore"
77
91
  - ".travis.yml"
92
+ - CHANGELOG.md
78
93
  - Gemfile
79
94
  - LICENSE
80
95
  - README.md
81
96
  - Rakefile
82
97
  - fluent-plugin-mqtt-io.gemspec
83
98
  - lib/fluent/plugin/in_mqtt.rb
84
- - lib/fluent/plugin/mqtt_output_mixin.rb
85
99
  - lib/fluent/plugin/out_mqtt.rb
86
- - lib/fluent/plugin/out_mqtt_buf.rb
87
100
  - test/helper.rb
101
+ - test/plugin/test_in_mqtt.rb
102
+ - test/plugin/test_out_mqtt.rb
88
103
  homepage: https://github.com/toyokazu/fluent-plugin-mqtt-io
89
104
  licenses:
90
- - MIT
105
+ - Apache License Version 2.0
91
106
  metadata: {}
92
107
  post_install_message:
93
108
  rdoc_options: []
@@ -97,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
112
  requirements:
98
113
  - - ">="
99
114
  - !ruby/object:Gem::Version
100
- version: '0'
115
+ version: 2.2.0
101
116
  required_rubygems_version: !ruby/object:Gem::Requirement
102
117
  requirements:
103
118
  - - ">="
@@ -105,9 +120,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
120
  version: '0'
106
121
  requirements: []
107
122
  rubyforge_project:
108
- rubygems_version: 2.4.5.1
123
+ rubygems_version: 2.6.8
109
124
  signing_key:
110
125
  specification_version: 4
111
126
  summary: fluentd input/output plugin for mqtt broker
112
127
  test_files:
113
128
  - test/helper.rb
129
+ - test/plugin/test_in_mqtt.rb
130
+ - test/plugin/test_out_mqtt.rb
@@ -1,155 +0,0 @@
1
- module Fluent
2
- module MqttOutputMixin
3
- # config_param defines a parameter. You can refer a parameter via @path instance variable
4
- # Without :default, a parameter is required.
5
-
6
- def self.included(base)
7
- base.config_param :port, :integer, :default => 1883
8
- base.config_param :host, :string, :default => '127.0.0.1'
9
- base.config_param :username, :string, :default => nil
10
- base.config_param :password, :string, :default => nil
11
- base.config_param :keep_alive, :integer, :default => 15
12
- base.config_param :ssl, :bool, :default => nil
13
- base.config_param :ca_file, :string, :default => nil
14
- base.config_param :key_file, :string, :default => nil
15
- base.config_param :cert_file, :string, :default => nil
16
- base.config_param :time_key, :string, :default => 'time'
17
- base.config_param :time_format, :string, :default => nil
18
- base.config_param :topic_rewrite_pattern, :string, :default => nil
19
- base.config_param :topic_rewrite_replacement, :string, :default => nil
20
- base.config_param :bulk_trans_sep, :string, :default => "\t"
21
- base.config_param :send_time, :bool, :default => false
22
- base.config_param :send_time_key, :string, :default => "send_time"
23
- base.config_param :initial_interval, :integer, :default => 1
24
- base.config_param :retry_inc_ratio, :integer, :default => 2
25
- end
26
-
27
- require 'mqtt'
28
-
29
- # This method is called before starting.
30
- # 'conf' is a Hash that includes configuration parameters.
31
- # If the configuration is invalid, raise Fluent::ConfigError.
32
- def configure(conf)
33
- super
34
- init_retry_interval
35
- end
36
-
37
- def init_retry_interval
38
- @retry_interval = @initial_interval
39
- end
40
-
41
- def increment_retry_interval
42
- @retry_interval = @retry_interval * @retry_inc_ratio
43
- end
44
-
45
- def sleep_retry_interval(e, message)
46
- $log.error "#{message},#{e.class},#{e.message}"
47
- $log.error "Retry in #{@retry_interval} sec"
48
- sleep @retry_interval
49
- increment_retry_interval
50
- end
51
-
52
- # This method is called when starting.
53
- # Open sockets or files here.
54
- def start
55
- super
56
-
57
- $log.debug "start mqtt #{@host}"
58
- opts = {
59
- host: @host,
60
- port: @port,
61
- username: @username,
62
- password: @password,
63
- keep_alive: @keep_alive
64
- }
65
- opts[:ssl] = @ssl if @ssl
66
- opts[:ca_file] = @ca_file if @ca_file
67
- opts[:cert_file] = @cert_file if @cert_file
68
- opts[:key_file] = @key_file if @key_file
69
- # In order to handle Exception raised from reading Thread
70
- # in MQTT::Client caused by network disconnection (during read_byte),
71
- # @connect_thread generates connection.
72
- @client = MQTT::Client.new(opts)
73
- @connect_thread = Thread.new do
74
- while (true)
75
- begin
76
- @client.disconnect if @client.connected?
77
- @client.connect
78
- init_retry_interval
79
- sleep
80
- rescue MQTT::ProtocolException => e
81
- sleep_retry_interval(e, "Protocol error occurs.")
82
- next
83
- rescue Timeout::Error => e
84
- sleep_retry_interval(e, "Timeout error occurs.")
85
- next
86
- rescue SystemCallError => e
87
- sleep_retry_interval(e, "System call error occurs.")
88
- next
89
- rescue StandardError=> e
90
- sleep_retry_interval(e, "The other error occurs.")
91
- next
92
- end
93
- end
94
- end
95
- end
96
-
97
- # This method is called when shutting down.
98
- # Shutdown the thread and close sockets or files here.
99
- def shutdown
100
- super
101
-
102
- @client.disconnect
103
- end
104
-
105
- def format_time(time)
106
- case @time_format
107
- when nil then
108
- # default format is integer value
109
- time
110
- when "iso8601" then
111
- # iso8601 format
112
- Time.at(time).iso8601
113
- else
114
- # specified strftime format
115
- Time.at(time).strftime(@time_format)
116
- end
117
- end
118
-
119
- def timestamp_hash(time)
120
- if @time_key.nil?
121
- {}
122
- else
123
- {@time_key => format_time(time)}
124
- end
125
- end
126
-
127
- def add_send_time(record)
128
- if @send_time
129
- # send_time is recorded in ms
130
- record.merge({@send_time_key => Time.now.instance_eval { self.to_i * 1000 + (usec/1000) }})
131
- else
132
- record
133
- end
134
- end
135
-
136
-
137
- def rewrite_tag(tag)
138
- if @topic_rewrite_pattern.nil?
139
- tag.gsub("\.", "/")
140
- else
141
- tag.gsub("\.", "/").gsub(Regexp.new(@topic_rewrite_pattern), @topic_rewrite_replacement)
142
- end
143
- end
144
-
145
- def json_parse(message)
146
- begin
147
- y = Yajl::Parser.new
148
- y.parse(message)
149
- rescue
150
- $log.error "JSON parse error", :error => $!.to_s, :error_class => $!.class.to_s
151
- $log.warn_backtrace $!.backtrace
152
- end
153
- end
154
- end
155
- end
@@ -1,36 +0,0 @@
1
- module Fluent
2
- class MqttBufferedOutput < BufferedOutput
3
- require 'fluent/plugin/mqtt_output_mixin'
4
- include Fluent::MqttOutputMixin
5
-
6
- # First, register the plugin. NAME is the name of this plugin
7
- # and identifies the plugin in the configuration file.
8
- Fluent::Plugin.register_output('mqtt_buf', self)
9
-
10
- # This method is called when an event reaches to Fluentd.
11
- # Convert the event to a raw string.
12
- def format(tag, time, record)
13
- [tag, time, record].to_msgpack
14
- end
15
-
16
- # This method is called every flush interval. Write the buffer chunk
17
- # to files or databases here.
18
- # 'chunk' is a buffer chunk that includes multiple formatted
19
- # events. You can use 'data = chunk.read' to get all events and
20
- # 'chunk.open {|io| ... }' to get IO objects.
21
- #
22
- # NOTE! This method is called by internal thread, not Fluentd's main thread. So IO wait doesn't affect other plugins.
23
- def write(chunk)
24
- messages = {}
25
- chunk.msgpack_each do |tag, time, record|
26
- messages[tag] = [] if messages[tag].nil?
27
- messages[tag] << add_send_time(record).merge(timestamp_hash(time))
28
- end
29
- messages.keys.each do |tag|
30
- $log.debug "Thread ID: #{Thread.current.object_id}, topic: #{rewrite_tag(tag)}, message: #{messages[tag]}"
31
- @client.publish(rewrite_tag(tag), messages[tag].map {|m| m.to_json}.join(@bulk_trans_sep))
32
- end
33
- $log.flush
34
- end
35
- end
36
- end