kodama 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 +17 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +1 -0
- data/kodama.gemspec +28 -0
- data/lib/kodama.rb +1 -0
- data/lib/kodama/client.rb +207 -0
- data/lib/kodama/version.rb +3 -0
- data/spec/lib/client_spec.rb +129 -0
- data/spec/spec_helper.rb +1 -0
- metadata +181 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Yusuke Mito
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Kodama
|
2
|
+
|
3
|
+
Kodama is a MySQL replication listener based on [ruby-binlog](https://bitbucket.org/winebarrel/ruby-binlog/overview).
|
4
|
+
Kodama provides a simple DSL to easily write your own replication listener.
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
- Provides simple DSL for writing binlog event handlers
|
9
|
+
- Automatically restarts from the saved binlog position
|
10
|
+
- Attempts to reconnect to MySQL when the connection is somehow teminated
|
11
|
+
|
12
|
+
These features allow developers to focus on writing their own replication logic rather than having to spend time figuring things out.
|
13
|
+
|
14
|
+
## Kodama Benefits
|
15
|
+
|
16
|
+
Kodama can be used to replicate MySQL updates to other data stores, arbitrary software or even a flat file. The sole purpose of Kodama is to provide a convenient way to reflect the database updates to other components in your system.
|
17
|
+
|
18
|
+
- Replicate from MySQL to Postgres
|
19
|
+
- Replicate between tables with different schema
|
20
|
+
- Sync production data to development DB while masking privacy information
|
21
|
+
- Realtime full text index update
|
22
|
+
|
23
|
+
## Dependencies
|
24
|
+
|
25
|
+
This gem links against MySQL's libreplication C shared library. You need to first install the [mysql-replication-listener](https://launchpad.net/mysql-replication-listener) package.
|
26
|
+
|
27
|
+
But official repository has some bugs. It is recommended to use [winebarrel's patched version](https://bitbucket.org/winebarrel/ruby-binlog/downloads) (There are rpm package and homebrew formula).
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Add this line to your application's Gemfile:
|
32
|
+
|
33
|
+
gem 'kodama'
|
34
|
+
|
35
|
+
And then execute:
|
36
|
+
|
37
|
+
$ bundle
|
38
|
+
|
39
|
+
Or install it yourself as:
|
40
|
+
|
41
|
+
$ gem install kodama
|
42
|
+
|
43
|
+
### binlog_format
|
44
|
+
|
45
|
+
It is recommended to set the mysqld binlog_format option to ``ROW``. This is because the ``ROW`` format allows Kodama to pickup every single updates made to the database.
|
46
|
+
|
47
|
+
```sql
|
48
|
+
SET GLOBAL binlog_format = 'ROW';
|
49
|
+
```
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
### Simple client
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'kodama'
|
57
|
+
|
58
|
+
Kodama::Client.start(:host => '127.0.0.1', :username => 'user') do |c|
|
59
|
+
c.binlog_position_file = 'position.log'
|
60
|
+
c.log_level = :info # [:debug|:info|:warn|:error|:fatal]
|
61
|
+
c.connection_retry_limit = 100 # times
|
62
|
+
c.connection_retry_wait = 3 # second
|
63
|
+
|
64
|
+
c.on_query_event do |event|
|
65
|
+
p event.query
|
66
|
+
end
|
67
|
+
|
68
|
+
c.on_row_event do |event|
|
69
|
+
p event.rows
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
### Replicate to redis
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'rubygems'
|
78
|
+
require 'kodama'
|
79
|
+
require 'thread'
|
80
|
+
require 'json'
|
81
|
+
require 'redis'
|
82
|
+
|
83
|
+
class Worker
|
84
|
+
attr_accessor :queue
|
85
|
+
def initialize
|
86
|
+
@queue = Queue.new
|
87
|
+
@redis = Redis.new
|
88
|
+
end
|
89
|
+
|
90
|
+
def start
|
91
|
+
Thread.start do
|
92
|
+
loop do
|
93
|
+
event = @queue.pop
|
94
|
+
record_id = get_row(event)[0] # first column is id
|
95
|
+
@redis.set "#{event.table_name}_#{record_id}", event.rows.to_json
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_row(event)
|
101
|
+
case event.event_type
|
102
|
+
when /Write/, /Delete/
|
103
|
+
event.rows[0] # [row]
|
104
|
+
when /Update/
|
105
|
+
event.rows[0][1] # [[old_row, new_row]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
worker = Worker.new
|
112
|
+
worker.start
|
113
|
+
|
114
|
+
Kodama::Client.start(:host => '127.0.0.1', :username => 'user') do |c|
|
115
|
+
c.binlog_position_file = 'position.log'
|
116
|
+
|
117
|
+
c.on_row_event do |event|
|
118
|
+
worker.queue << event
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
## Configuration
|
124
|
+
|
125
|
+
### binlog_position_file
|
126
|
+
|
127
|
+
Sets the filename to save the binlog position.
|
128
|
+
Kodama will read this file and resume listening from the stored position.
|
129
|
+
|
130
|
+
### log_level
|
131
|
+
|
132
|
+
Set logger's log level.
|
133
|
+
It accepts ``:debug``, ``:info``, ``:warn``, ``:error``, ``:fatal``.
|
134
|
+
|
135
|
+
### connection_retry_limit, connection_retry_wait
|
136
|
+
|
137
|
+
If for some reason the connection to MySQL is terminated, Kodama will attempt to reconnect ``connection_retry_limit`` times, while waiting ``connection_retry_wait`` seconds between attempts.
|
138
|
+
|
139
|
+
## Authors
|
140
|
+
|
141
|
+
Yusuke Mito, Genki Sugawara
|
142
|
+
|
143
|
+
## Based On
|
144
|
+
|
145
|
+
- [ruby-binlog](https://bitbucket.org/winebarrel/ruby-binlog)
|
146
|
+
- [mysql-replication-listener](https://launchpad.net/mysql-replication-listener)
|
147
|
+
|
148
|
+
## Contributing
|
149
|
+
|
150
|
+
1. Fork it
|
151
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
152
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
153
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
154
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/kodama.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kodama/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "kodama"
|
8
|
+
gem.version = Kodama::VERSION
|
9
|
+
gem.authors = ["Yusuke Mito"]
|
10
|
+
gem.email = ["y310.1984@gmail.com"]
|
11
|
+
gem.description = %q{ruby-binlog based MySQL replication listener}
|
12
|
+
gem.summary = %q{ruby-binlog based MySQL replication listener}
|
13
|
+
gem.homepage = "https://github.com/y310/kodama"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'ruby-binlog', '>= 0.1.8'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
|
+
gem.add_development_dependency 'pry'
|
25
|
+
gem.add_development_dependency 'pry-nav'
|
26
|
+
gem.add_development_dependency 'guard-rspec', '2.1.2'
|
27
|
+
gem.add_development_dependency 'rb-fsevent', '~> 0.9.1'
|
28
|
+
end
|
data/lib/kodama.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'kodama/client'
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'binlog'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Kodama
|
7
|
+
class Client
|
8
|
+
LOG_LEVEL = {
|
9
|
+
:fatal => Logger::FATAL,
|
10
|
+
:error => Logger::ERROR,
|
11
|
+
:warn => Logger::WARN,
|
12
|
+
:info => Logger::INFO,
|
13
|
+
:debug => Logger::DEBUG,
|
14
|
+
}
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def start(options = {}, &block)
|
18
|
+
client = self.new(mysql_url(options))
|
19
|
+
block.call(client)
|
20
|
+
client.start
|
21
|
+
end
|
22
|
+
|
23
|
+
def mysql_url(options = {})
|
24
|
+
password = options[:password] ? ":#{options[:password]}" : nil
|
25
|
+
port = options[:port] ? ":#{options[:port]}" : nil
|
26
|
+
"mysql://#{options[:username]}#{password}@#{options[:host]}#{port}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(url)
|
31
|
+
@url = url
|
32
|
+
@binlog_info = BinlogInfo.new
|
33
|
+
@retry_info = RetryInfo.new(:limit => 100, :wait => 3)
|
34
|
+
@callbacks = {}
|
35
|
+
@logger = Logger.new(STDOUT)
|
36
|
+
|
37
|
+
self.log_level = :info
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_query_event(&block); @callbacks[:on_query_event] = block; end
|
41
|
+
def on_rotate_event(&block); @callbacks[:on_rotate_event] = block; end
|
42
|
+
def on_int_var_event(&block); @callbacks[:on_int_var_event] = block; end
|
43
|
+
def on_user_var_event(&block); @callbacks[:on_user_var_event] = block; end
|
44
|
+
def on_format_event(&block); @callbacks[:on_format_event] = block; end
|
45
|
+
def on_xid(&block); @callbacks[:on_xid] = block; end
|
46
|
+
def on_table_map_event(&block); @callbacks[:on_table_map_event] = block; end
|
47
|
+
def on_row_event(&block); @callbacks[:on_row_event] = block; end
|
48
|
+
def on_incident_event(&block); @callbacks[:on_incident_event] = block; end
|
49
|
+
def on_unimplemented_event(&block); @callbacks[:on_unimplemented_event] = block; end
|
50
|
+
|
51
|
+
def binlog_position_file=(filename)
|
52
|
+
@position_file = position_file(filename)
|
53
|
+
@binlog_info.load!(@position_file)
|
54
|
+
end
|
55
|
+
|
56
|
+
def connection_retry_wait=(wait)
|
57
|
+
@retry_info.wait = wait
|
58
|
+
end
|
59
|
+
|
60
|
+
def connection_retry_limit=(limit)
|
61
|
+
@retry_info.limit = limit
|
62
|
+
end
|
63
|
+
|
64
|
+
def log_level=(level)
|
65
|
+
@logger.level = LOG_LEVEL[level]
|
66
|
+
end
|
67
|
+
|
68
|
+
def binlog_client(url)
|
69
|
+
Binlog::Client.new(url)
|
70
|
+
end
|
71
|
+
|
72
|
+
def position_file(filename)
|
73
|
+
PositionFile.new(filename)
|
74
|
+
end
|
75
|
+
|
76
|
+
def connection_retry_count
|
77
|
+
@retry_info.count
|
78
|
+
end
|
79
|
+
|
80
|
+
def start
|
81
|
+
begin
|
82
|
+
client = binlog_client(@url)
|
83
|
+
raise Binlog::Error, 'MySQL server has gone away' unless client.connect
|
84
|
+
@retry_info.count_reset
|
85
|
+
|
86
|
+
if @binlog_info.valid?
|
87
|
+
client.set_position(@binlog_info.filename, @binlog_info.position)
|
88
|
+
end
|
89
|
+
|
90
|
+
while event = client.wait_for_next_event
|
91
|
+
@binlog_info.position = event.next_position
|
92
|
+
case event
|
93
|
+
when Binlog::QueryEvent
|
94
|
+
callback :on_query_event, event
|
95
|
+
@binlog_info.save(@position_file)
|
96
|
+
when Binlog::RotateEvent
|
97
|
+
callback :on_rotate_event, event
|
98
|
+
@binlog_info.filename = event.binlog_file
|
99
|
+
@binlog_info.position = event.binlog_pos
|
100
|
+
@binlog_info.save(@position_file)
|
101
|
+
when Binlog::IntVarEvent
|
102
|
+
callback :on_int_var_event, event
|
103
|
+
when Binlog::UserVarEvent
|
104
|
+
callback :on_user_var_event, event
|
105
|
+
when Binlog::FormatEvent
|
106
|
+
callback :on_format_event, event
|
107
|
+
when Binlog::Xid
|
108
|
+
callback :on_xid, event
|
109
|
+
when Binlog::TableMapEvent
|
110
|
+
callback :on_table_map_event, event
|
111
|
+
when Binlog::RowEvent
|
112
|
+
callback :on_row_event, event
|
113
|
+
@binlog_info.save(@position_file)
|
114
|
+
when Binlog::IncidentEvent
|
115
|
+
callback :on_incident_event, event
|
116
|
+
when Binlog::UnimplementedEvent
|
117
|
+
callback :on_unimplemented_event, event
|
118
|
+
else
|
119
|
+
@logger.debug "Not Implemented: #{event.event_type}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
rescue Binlog::Error => e
|
123
|
+
@logger.debug e
|
124
|
+
if client.closed? && @retry_info.retryable?
|
125
|
+
sleep @retry_info.wait
|
126
|
+
@retry_info.count_up
|
127
|
+
retry
|
128
|
+
end
|
129
|
+
raise e
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
def callback(name, *args)
|
135
|
+
if @callbacks[name]
|
136
|
+
instance_exec *args, &@callbacks[name]
|
137
|
+
else
|
138
|
+
@logger.debug "Unhandled: #{name}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class BinlogInfo
|
143
|
+
attr_accessor :filename, :position
|
144
|
+
|
145
|
+
def initialize(filename = nil, position = nil)
|
146
|
+
@filename = filename
|
147
|
+
@position = position
|
148
|
+
end
|
149
|
+
|
150
|
+
def valid?
|
151
|
+
@filename && @position
|
152
|
+
end
|
153
|
+
|
154
|
+
def save(position_file = nil)
|
155
|
+
if position_file
|
156
|
+
position_file.update(@filename, @position)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def load!(position_file)
|
161
|
+
@filename, @position = position_file.read
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class RetryInfo
|
166
|
+
attr_accessor :count, :limit, :wait
|
167
|
+
def initialize(options = {})
|
168
|
+
@wait = options[:wait] || 3
|
169
|
+
@limit = options[:limit] || 100
|
170
|
+
@count = 0
|
171
|
+
end
|
172
|
+
|
173
|
+
def retryable?
|
174
|
+
@count < @limit
|
175
|
+
end
|
176
|
+
|
177
|
+
def count_up
|
178
|
+
@count += 1
|
179
|
+
end
|
180
|
+
|
181
|
+
def count_reset
|
182
|
+
@count = 0
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class PositionFile
|
187
|
+
def initialize(filename)
|
188
|
+
@file = open(filename, File::RDWR|File::CREAT)
|
189
|
+
@file.sync = true
|
190
|
+
end
|
191
|
+
|
192
|
+
def update(filename, position)
|
193
|
+
@file.pos = 0
|
194
|
+
@file.write "#{filename}\t#{position}"
|
195
|
+
@file.truncate @file.pos
|
196
|
+
end
|
197
|
+
|
198
|
+
def read
|
199
|
+
@file.pos = 0
|
200
|
+
if line = @file.gets
|
201
|
+
filename, position = line.split("\t")
|
202
|
+
[filename, position.to_i]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kodama::Client do
|
4
|
+
describe '.mysql_url' do
|
5
|
+
def mysql_url(options)
|
6
|
+
Kodama::Client.mysql_url(options)
|
7
|
+
end
|
8
|
+
|
9
|
+
it do
|
10
|
+
mysql_url(:username => 'user', :host => 'example.com').should == 'mysql://user@example.com'
|
11
|
+
mysql_url(:username => 'user', :host => 'example.com',
|
12
|
+
:password => 'password', :port => 3306).should == 'mysql://user:password@example.com:3306'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#start' do
|
17
|
+
class TestBinlogClient
|
18
|
+
attr_accessor :connect
|
19
|
+
def initialize(events = [], connect = true)
|
20
|
+
@events = events
|
21
|
+
@connect = connect
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait_for_next_event
|
25
|
+
event = @events.shift
|
26
|
+
stub_event(event)
|
27
|
+
event
|
28
|
+
end
|
29
|
+
|
30
|
+
def closed?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def stub_event(target_event)
|
35
|
+
target_event_class = target_event.instance_variable_get('@name')
|
36
|
+
[Binlog::QueryEvent, Binlog::RowEvent, Binlog::RotateEvent, Binlog::Xid].each do |event|
|
37
|
+
if event == target_event_class
|
38
|
+
event.stub(:===).and_return { true }
|
39
|
+
else
|
40
|
+
# :=== is stubbed
|
41
|
+
if event.method(:===).owner != Module
|
42
|
+
event.unstub(:===)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestPositionFile
|
50
|
+
def update(filename, position)
|
51
|
+
end
|
52
|
+
|
53
|
+
def read
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def stub_binlog_client(events = [], connect = true)
|
58
|
+
client.stub(:binlog_client).and_return { TestBinlogClient.new(events, connect) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def stub_position_file(position_file = nil)
|
62
|
+
client.stub(:position_file).and_return { position_file || TestPositionFile.new }
|
63
|
+
end
|
64
|
+
|
65
|
+
let(:client) { Kodama::Client.new('mysql://user@host') }
|
66
|
+
|
67
|
+
let(:rotate_event) do
|
68
|
+
mock(Binlog::RotateEvent).tap do |event|
|
69
|
+
event.stub(:next_position).and_return { 0 }
|
70
|
+
event.stub(:binlog_file).and_return { 'binlog' }
|
71
|
+
event.stub(:binlog_pos).and_return { 100 }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
let(:query_event) do
|
76
|
+
mock(Binlog::QueryEvent).tap do |event|
|
77
|
+
event.stub(:next_position).and_return { 200 }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
let(:row_event) do
|
82
|
+
mock(Binlog::RowEvent).tap do |event|
|
83
|
+
event.stub(:next_position).and_return { 300 }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:xid_event) do
|
88
|
+
mock(Binlog::Xid).tap do |event|
|
89
|
+
event.stub(:next_position).and_return { 400 }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should receive query_event' do
|
94
|
+
stub_binlog_client([query_event])
|
95
|
+
expect {|block|
|
96
|
+
client.on_query_event(&block)
|
97
|
+
client.start
|
98
|
+
}.to yield_with_args(query_event)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should receive row_event' do
|
102
|
+
stub_binlog_client([row_event])
|
103
|
+
expect {|block|
|
104
|
+
client.on_row_event(&block)
|
105
|
+
client.start
|
106
|
+
}.to yield_with_args(row_event)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'position is saved only on row, query and rotate event' do
|
110
|
+
stub_binlog_client([rotate_event, query_event, row_event, xid_event])
|
111
|
+
position_file = TestPositionFile.new.tap do |pf|
|
112
|
+
pf.should_receive(:update).with('binlog', 100).once.ordered
|
113
|
+
pf.should_receive(:update).with('binlog', 200).once.ordered
|
114
|
+
pf.should_receive(:update).with('binlog', 300).once.ordered
|
115
|
+
end
|
116
|
+
stub_position_file(position_file)
|
117
|
+
client.binlog_position_file = 'test'
|
118
|
+
client.start
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'retry exactly specifeid times' do
|
122
|
+
stub_binlog_client([query_event], false)
|
123
|
+
client.connection_retry_limit = 2
|
124
|
+
client.connection_retry_wait = 0.1
|
125
|
+
expect { client.start }.to raise_error(Binlog::Error)
|
126
|
+
client.connection_retry_count.should == 2
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'kodama'
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kodama
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Yusuke Mito
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-11-28 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: ruby-binlog
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 11
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 1
|
32
|
+
- 8
|
33
|
+
version: 0.1.8
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: pry
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
type: :development
|
77
|
+
version_requirements: *id004
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry-nav
|
80
|
+
prerelease: false
|
81
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id005
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: guard-rspec
|
94
|
+
prerelease: false
|
95
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - "="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 15
|
101
|
+
segments:
|
102
|
+
- 2
|
103
|
+
- 1
|
104
|
+
- 2
|
105
|
+
version: 2.1.2
|
106
|
+
type: :development
|
107
|
+
version_requirements: *id006
|
108
|
+
- !ruby/object:Gem::Dependency
|
109
|
+
name: rb-fsevent
|
110
|
+
prerelease: false
|
111
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ~>
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 57
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
- 9
|
120
|
+
- 1
|
121
|
+
version: 0.9.1
|
122
|
+
type: :development
|
123
|
+
version_requirements: *id007
|
124
|
+
description: ruby-binlog based MySQL replication listener
|
125
|
+
email:
|
126
|
+
- y310.1984@gmail.com
|
127
|
+
executables: []
|
128
|
+
|
129
|
+
extensions: []
|
130
|
+
|
131
|
+
extra_rdoc_files: []
|
132
|
+
|
133
|
+
files:
|
134
|
+
- .gitignore
|
135
|
+
- Gemfile
|
136
|
+
- Guardfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- kodama.gemspec
|
141
|
+
- lib/kodama.rb
|
142
|
+
- lib/kodama/client.rb
|
143
|
+
- lib/kodama/version.rb
|
144
|
+
- spec/lib/client_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
homepage: https://github.com/y310/kodama
|
147
|
+
licenses: []
|
148
|
+
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
hash: 3
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
requirements: []
|
173
|
+
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 1.8.24
|
176
|
+
signing_key:
|
177
|
+
specification_version: 3
|
178
|
+
summary: ruby-binlog based MySQL replication listener
|
179
|
+
test_files:
|
180
|
+
- spec/lib/client_spec.rb
|
181
|
+
- spec/spec_helper.rb
|