fluent-plugin-mysql-replicator 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ vendor/*
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.0.0
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-mysql-replicator.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2013- Kentaro Yoshida
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # fluent-plugin-mysql-replicator [![Build Status](https://travis-ci.org/y-ken/fluent-plugin-mysql-replicator.png?branch=master)](https://travis-ci.org/y-ken/fluent-plugin-mysql-replicator)
2
+
3
+ ## Overview
4
+
5
+ Fluentd input plugin to track insert/update/delete event from MySQL database server.
6
+
7
+ ## Installation
8
+
9
+ `````
10
+ ### native gem
11
+ gem install fluent-plugin-mysql-replicator
12
+
13
+ ### td-agent gem
14
+ /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-mysql-replicator
15
+ `````
16
+
17
+ ## Tutorial
18
+
19
+ #### configuration
20
+
21
+ `````
22
+ <source>
23
+ type mysql_replicator
24
+ host localhost
25
+ username your_mysql_user
26
+ password your_mysql_password
27
+ database myweb
28
+ interval 5s
29
+ tag replicator
30
+ query SELECT id, text from search_test
31
+ </source>
32
+
33
+ <match replicator.*>
34
+ type stdout
35
+ </match>
36
+ `````
37
+
38
+ #### sample query
39
+
40
+ `````
41
+ $ mysql -e "create database myweb"
42
+ $ mysql myweb -e "create table search_test(id int auto_increment, text text, PRIMARY KEY (id))"
43
+ $ sleep 10
44
+ $ mysql myweb -e "insert into search_test(text) values('aaa')"
45
+ $ sleep 10
46
+ $ mysql myweb -e "update search_test set text='bbb' where text = 'aaa'"
47
+ $ sleep 10
48
+ $ mysql myweb -e "delete from search_test where text='bbb'"
49
+ `````
50
+
51
+ #### result
52
+
53
+ `````
54
+ $ tail -f /var/log/td-agent/td-agent.log
55
+ 2013-11-25 18:22:25 +0900 replicator.insert: {"id":"1","text":"aaa"}
56
+ 2013-11-25 18:22:35 +0900 replicator.update: {"id":"1","text":"bbb"}
57
+ 2013-11-25 18:22:45 +0900 replicator.delete: {"id":"1"}
58
+ `````
59
+
60
+ ## TODO
61
+
62
+ Pull requests are very welcome!!
63
+
64
+ ## Copyright
65
+
66
+ Copyright © 2013- Kentaro Yoshida ([@yoshi_ken](https://twitter.com/yoshi_ken))
67
+
68
+ ## License
69
+
70
+ Apache License, Version 2.0
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ Rake::TestTask.new(:test) do |test|
4
+ test.libs << 'lib' << 'test'
5
+ test.pattern = 'test/**/test_*.rb'
6
+ test.verbose = true
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = "fluent-plugin-mysql-replicator"
4
+ s.version = "0.0.1"
5
+ s.authors = ["Kentaro Yoshida"]
6
+ s.email = ["y.ken.studio@gmail.com"]
7
+ s.homepage = "https://github.com/y-ken/fluent-plugin-mysql-replicator"
8
+ s.summary = %q{Fluentd input plugin to track insert/update/delete event from MySQL database server.}
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ s.require_paths = ["lib"]
14
+
15
+ s.add_development_dependency "rake"
16
+ s.add_runtime_dependency "fluentd"
17
+ s.add_runtime_dependency "mysql2"
18
+ end
@@ -0,0 +1,109 @@
1
+ module Fluent
2
+ class MysqlReplicatorInput < Fluent::Input
3
+ Plugin.register_input('mysql_replicator', self)
4
+
5
+ def initialize
6
+ require 'mysql2'
7
+ require 'digest/sha1'
8
+ super
9
+ end
10
+
11
+ config_param :host, :string, :default => 'localhost'
12
+ config_param :port, :integer, :default => 3306
13
+ config_param :username, :string, :default => 'root'
14
+ config_param :password, :string, :default => nil
15
+ config_param :database, :string, :default => nil
16
+ config_param :encoding, :string, :default => 'utf8'
17
+ config_param :interval, :string, :default => '1m'
18
+ config_param :tag, :string
19
+ config_param :query, :string
20
+ config_param :primary_key, :string, :default => 'id'
21
+
22
+ def configure(conf)
23
+ super
24
+ @interval = Config.time_value(@interval)
25
+ $log.info "adding mysql_replicator job: [#{@query}] interval: #{@interval}sec"
26
+ end
27
+
28
+ def start
29
+ @thread = Thread.new(&method(:run))
30
+ end
31
+
32
+ def shutdown
33
+ Thread.kill(@thread)
34
+ end
35
+
36
+ def run
37
+ begin
38
+ poll
39
+ rescue StandardError => e
40
+ $log.error "error: #{e.message}"
41
+ $log.error e.backtrace.join("\n")
42
+ end
43
+ end
44
+
45
+ def poll
46
+ table_hash = Hash.new
47
+ ids = Array.new
48
+ loop do
49
+ previous_ids = ids
50
+ current_ids = Array.new
51
+ query(@query).each do |row|
52
+ current_ids << row[@primary_key]
53
+ current_hash = Digest::SHA1.hexdigest(row.flatten.join)
54
+ if !table_hash.include?(row[@primary_key])
55
+ emit_record(:insert, row)
56
+ elsif table_hash[row[@primary_key]] != current_hash
57
+ emit_record(:update, row)
58
+ end
59
+ table_hash[row[@primary_key]] = current_hash
60
+ end
61
+ ids = current_ids
62
+ deleted_ids = previous_ids - current_ids
63
+ if deleted_ids.count > 0
64
+ hash_delete_by_list(table_hash, deleted_ids)
65
+ deleted_ids.each {|id| emit_record(:delete, {@primary_key => id})}
66
+ end
67
+ sleep @interval
68
+ end
69
+ end
70
+
71
+ def hash_delete_by_list (hash, deleted_keys)
72
+ deleted_keys.each{|k| hash.delete(k)}
73
+ end
74
+
75
+ def emit_record(type, record)
76
+ tag = "#{@tag}.#{type.to_s}"
77
+ Engine.emit(tag, Engine.now, record)
78
+ end
79
+
80
+ def query(query)
81
+ @mysql ||= get_connection
82
+ begin
83
+ return @mysql.query(query, :cast => false, :cache_rows => false)
84
+ rescue Exception => e
85
+ $log.warn "mysql_replicator: #{e}"
86
+ sleep @interval
87
+ retry
88
+ end
89
+ end
90
+
91
+ def get_connection
92
+ begin
93
+ return Mysql2::Client.new({
94
+ :host => @host,
95
+ :port => @port,
96
+ :username => @username,
97
+ :password => @password,
98
+ :database => @database,
99
+ :encoding => @encoding,
100
+ :reconnect => true
101
+ })
102
+ rescue Exception => e
103
+ $log.warn "mysql_replicator: #{e}"
104
+ sleep @interval
105
+ retry
106
+ end
107
+ end
108
+ end
109
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/plugin/in_mysql_replicator'
26
+
27
+ class Test::Unit::TestCase
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'helper'
2
+
3
+ class MysqlReplicatorInputTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ host localhost
10
+ port 3306
11
+ interval 30
12
+ tag input.mysql
13
+ query SELECT id, text from search_text
14
+ record_hostname yes
15
+ ]
16
+
17
+ def create_driver(conf=CONFIG,tag='test')
18
+ Fluent::Test::OutputTestDriver.new(Fluent::MysqlReplicatorInput, tag).configure(conf)
19
+ end
20
+
21
+ def test_configure
22
+ assert_raise(Fluent::ConfigError) {
23
+ d = create_driver('')
24
+ }
25
+ d = create_driver %[
26
+ host localhost
27
+ port 3306
28
+ interval 30
29
+ tag input.mysql
30
+ query SELECT id, text from search_text
31
+ ]
32
+ d.instance.inspect
33
+ assert_equal 'localhost', d.instance.host
34
+ assert_equal 3306, d.instance.port
35
+ assert_equal 30, d.instance.interval
36
+ assert_equal 'input.mysql', d.instance.tag
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-mysql-replicator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kentaro Yoshida
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: fluentd
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mysql2
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description:
63
+ email:
64
+ - y.ken.studio@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .travis.yml
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - fluent-plugin-mysql-replicator.gemspec
76
+ - lib/fluent/plugin/in_mysql_replicator.rb
77
+ - test/helper.rb
78
+ - test/plugin/test_in_mysql_replicator.rb
79
+ homepage: https://github.com/y-ken/fluent-plugin-mysql-replicator
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.23
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Fluentd input plugin to track insert/update/delete event from MySQL database
103
+ server.
104
+ test_files:
105
+ - test/helper.rb
106
+ - test/plugin/test_in_mysql_replicator.rb