murakumo 0.2.9 → 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.
- data/README +1 -1
- data/bin/mrkmctl.orig +5 -0
- data/bin/murakumo-install-init-script.orig +34 -0
- data/bin/murakumo.orig +5 -0
- data/etc/murakumo-update-config-for-ec2.orig +33 -0
- data/etc/murakumo.server +1 -0
- data/etc/murakumo.yml.example +1 -0
- data/lib/cli/murakumo_options.rb +3 -0
- data/lib/misc/murakumo_const.rb +1 -1
- data/lib/srv/murakumo_cloud.rb +51 -5
- data/lib/srv/murakumo_cloud.rb.orig +532 -0
- data/lib/srv/murakumo_server.rb +15 -1
- data/lib/srv/murakumo_server.rb.orig +101 -0
- metadata +20 -14
data/README
CHANGED
|
@@ -35,7 +35,7 @@ https://bitbucket.org/winebarrel/murakumo
|
|
|
35
35
|
shell> mrkmctl -L
|
|
36
36
|
IP address TTL Priority Activity Hostname
|
|
37
37
|
--------------- ------ -------- -------- ----------
|
|
38
|
-
10.11.12.13 60 Origin Active
|
|
38
|
+
10.11.12.13 60 Origin Active my-host
|
|
39
39
|
10.11.12.13 300 Master Active foo.bar
|
|
40
40
|
|
|
41
41
|
=== deletion of a record
|
data/bin/mrkmctl.orig
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
3
|
+
|
|
4
|
+
require 'misc/murakumo_const'
|
|
5
|
+
|
|
6
|
+
INIT_D_DIR = '/etc/init.d'
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
require 'rbconfig'
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
rescue LoadError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
gem_dir = nil
|
|
15
|
+
|
|
16
|
+
if defined?(Gem)
|
|
17
|
+
gem_dir = "#{Gem.dir}/gems"
|
|
18
|
+
elsif defined?(RbConfig)
|
|
19
|
+
gem_dir = RbConfig::CONFIG["rubylibdir"].sub(/\d+\.\d+\Z/) {|m| "gems/#{m}/gems"}
|
|
20
|
+
else
|
|
21
|
+
$stderr.puts 'error: gem dir is not found.'
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
bin_dir = ((RbConfig::CONFIG['bindir'] rescue nil) || '').strip
|
|
26
|
+
|
|
27
|
+
`cp -i #{gem_dir}/murakumo-#{Murakumo::VERSION}/etc/murakumo.server #{INIT_D_DIR}/murakumo`
|
|
28
|
+
`chmod 755 #{INIT_D_DIR}/murakumo`
|
|
29
|
+
|
|
30
|
+
unless bin_dir.empty?
|
|
31
|
+
`sed -i -r 's|^# processname:.*|# processname: #{bin_dir}/murakumo|' #{INIT_D_DIR}/murakumo`
|
|
32
|
+
`sed -i -r 's|^prog=.*|prog=#{bin_dir}/murakumo|' #{INIT_D_DIR}/murakumo`
|
|
33
|
+
`sed -i -r 's|^ctlprog=.*|ctlprog=#{bin_dir}/mrkmctl|' #{INIT_D_DIR}/murakumo`
|
|
34
|
+
end
|
data/bin/murakumo.orig
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
CURL='curl --retry 3 --retry-delay 0 --silent --fail'
|
|
3
|
+
IP_ADDR=`$CURL http://169.254.169.254/1.0/meta-data/local-ipv4`
|
|
4
|
+
CONF=/etc/murakumo.yml
|
|
5
|
+
|
|
6
|
+
if [ $? -ne 0 -o -z "$IP_ADDR" ]; then
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
if [ `grep ^host $CONF | egrep "host: $IP_ADDR\b" | wc -l` -eq 1 ]; then
|
|
11
|
+
exit
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
## gets a host name from user data.
|
|
15
|
+
#HOSTNAME=`$CURL http://169.254.169.254/1.0/user-data`
|
|
16
|
+
|
|
17
|
+
## gets a host name from name tag.
|
|
18
|
+
#export JAVA_HOME=/usr/java/default
|
|
19
|
+
#export EC2_HOME=/opt/ec2-api-tools
|
|
20
|
+
#EC2_PRIVATE_KEY=
|
|
21
|
+
#EC2_CERT=
|
|
22
|
+
#REGION=ap-northeast-1
|
|
23
|
+
#EC2DIN=$EC2_HOME/bin/ec2-describe-instances
|
|
24
|
+
#HOSTNAME=`$EC2DIN -K $EC2_PRIVATE_KEY -C $EC2_CERT --region $REGION -F "private-ip-address=$IP_ADDR" | awk -F'\t' '$1 == "TAG" && $4 == "Name" {print $5}'`
|
|
25
|
+
|
|
26
|
+
## gets a host name from meta data.
|
|
27
|
+
HOSTNAME=`$CURL http://169.254.169.254/1.0/meta-data/hostname`
|
|
28
|
+
|
|
29
|
+
if [ $? -ne 0 -o -z "$HOSTNAME" ]; then
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
sed -i.bak -r "s|^host:.*|host: $IP_ADDR, $HOSTNAME|" /etc/murakumo.yml
|
data/etc/murakumo.server
CHANGED
data/etc/murakumo.yml.example
CHANGED
data/lib/cli/murakumo_options.rb
CHANGED
|
@@ -88,6 +88,9 @@ def murakumo_parse_args
|
|
|
88
88
|
invalid_argument if (value || '').strip.empty?
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
desc 'enables the cache of a response'
|
|
92
|
+
option :enable_cache, '-e', '--enable-cache'
|
|
93
|
+
|
|
91
94
|
desc 'command of daemonize: {start|stop|restart|status}'
|
|
92
95
|
option :daemon, '-d', '--daemon CMD', :type => [:start, :stop, :restart, :status]
|
|
93
96
|
|
data/lib/misc/murakumo_const.rb
CHANGED
data/lib/srv/murakumo_cloud.rb
CHANGED
|
@@ -36,6 +36,8 @@ module Murakumo
|
|
|
36
36
|
# データベースを作成してレコードを更新
|
|
37
37
|
create_database
|
|
38
38
|
update(@address, datas)
|
|
39
|
+
|
|
40
|
+
# updateの後にホスト名をセットすること
|
|
39
41
|
@hostname = host_data.first
|
|
40
42
|
|
|
41
43
|
# ゴシップオブジェクトを生成
|
|
@@ -88,6 +90,9 @@ module Murakumo
|
|
|
88
90
|
@logger.warn('configuration of a health check is not right')
|
|
89
91
|
end
|
|
90
92
|
end
|
|
93
|
+
|
|
94
|
+
# キャッシュ
|
|
95
|
+
@cache = {} if options[:enable_cache]
|
|
91
96
|
end
|
|
92
97
|
|
|
93
98
|
# Control of service
|
|
@@ -116,6 +121,7 @@ module Murakumo
|
|
|
116
121
|
:socket => 'socket',
|
|
117
122
|
:max_ip_num => 'max-ip-num',
|
|
118
123
|
:domain => 'domain',
|
|
124
|
+
:enable_cache => lambda {|v| ['enable-cache', !!v] },
|
|
119
125
|
:log_path => 'log-path',
|
|
120
126
|
:log_level => 'log-level',
|
|
121
127
|
:gossip_port => 'gossip-port',
|
|
@@ -362,11 +368,51 @@ module Murakumo
|
|
|
362
368
|
# ドメインが指定されていたら削除
|
|
363
369
|
name.sub!(/\.#{Regexp.escape(@options[:domain])}\Z/i, '') if @options[:domain]
|
|
364
370
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
371
|
+
if @cache.nil?
|
|
372
|
+
# キャッシュを設定していないときはいつもの処理
|
|
373
|
+
@address_records = @db.execute(<<-EOS, name, ACTIVE) # シングルスレッドェ…
|
|
374
|
+
SELECT ip_address, ttl, priority FROM records
|
|
375
|
+
WHERE name = ? AND activity = ?
|
|
376
|
+
EOS
|
|
377
|
+
else
|
|
378
|
+
# キャッシュを設定しているとき
|
|
379
|
+
# キャッシュを検索
|
|
380
|
+
@address_records, cache_time = @cache[name]
|
|
381
|
+
now = Time.now
|
|
382
|
+
|
|
383
|
+
# キャッシュが見つからなかった・期限が切れていた場合
|
|
384
|
+
if @address_records.nil? or now > cache_time
|
|
385
|
+
# 既存のキャッシュは削除
|
|
386
|
+
@cache.delete(name)
|
|
387
|
+
|
|
388
|
+
# 普通に検索
|
|
389
|
+
@address_records = @db.execute(<<-EOS, name, ACTIVE) # シングルスレッドェ…
|
|
390
|
+
SELECT ip_address, ttl, priority FROM records
|
|
391
|
+
WHERE name = ? AND activity = ?
|
|
392
|
+
EOS
|
|
393
|
+
|
|
394
|
+
# レコードがあればキャッシュに入れる(ネガティブキャッシュはしない)
|
|
395
|
+
unless @address_records.empty?
|
|
396
|
+
min_ttl = nil
|
|
397
|
+
|
|
398
|
+
# レコードはハッシュに変換する
|
|
399
|
+
cache_records = @address_records.map do |i|
|
|
400
|
+
ip_address, ttl, priority = i.values_at('ip_address', 'ttl', 'priority')
|
|
401
|
+
|
|
402
|
+
if min_ttl.nil? or ttl < min_ttl
|
|
403
|
+
min_ttl = ttl
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
{'ip_address' => ip_address, 'ttl' => ttl, 'priority' => priority}
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# 最小値をExpire期限として設定
|
|
410
|
+
expire_time = now + min_ttl
|
|
411
|
+
|
|
412
|
+
@cache[name] = [cache_records, expire_time]
|
|
413
|
+
end
|
|
414
|
+
end # キャッシュが見つからなかった場合
|
|
415
|
+
end # @cache.nil?の判定
|
|
370
416
|
|
|
371
417
|
@address_records.length.nonzero?
|
|
372
418
|
end
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'rgossip2'
|
|
3
|
+
require 'sqlite3'
|
|
4
|
+
|
|
5
|
+
require 'srv/murakumo_health_checker'
|
|
6
|
+
require 'misc/murakumo_const'
|
|
7
|
+
|
|
8
|
+
module Murakumo
|
|
9
|
+
|
|
10
|
+
class Cloud
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
13
|
+
attr_reader :address
|
|
14
|
+
attr_reader :gossip
|
|
15
|
+
attr_reader :db
|
|
16
|
+
attr_reader :hostname
|
|
17
|
+
|
|
18
|
+
def initialize(options)
|
|
19
|
+
# オプションはインスタンス変数に保存
|
|
20
|
+
@options = options
|
|
21
|
+
|
|
22
|
+
# リソースレコードからホストのアドレスとデータを取り出す
|
|
23
|
+
host_data = options[:host]
|
|
24
|
+
@address = host_data.shift
|
|
25
|
+
host_data.concat [ORIGIN, ACTIVE]
|
|
26
|
+
alias_datas = options[:aliases].map {|r| r + [ACTIVE] }
|
|
27
|
+
@logger = options[:logger]
|
|
28
|
+
|
|
29
|
+
# 名前は小文字に変換
|
|
30
|
+
datas = ([host_data] + alias_datas).map do |i|
|
|
31
|
+
name, ttl, priority, activity = i
|
|
32
|
+
name = name.downcase
|
|
33
|
+
[name, ttl, priority, activity]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# データベースを作成してレコードを更新
|
|
37
|
+
create_database
|
|
38
|
+
update(@address, datas)
|
|
39
|
+
|
|
40
|
+
# updateの後にホスト名をセットすること
|
|
41
|
+
@hostname = host_data.first
|
|
42
|
+
|
|
43
|
+
# ゴシップオブジェクトを生成
|
|
44
|
+
@gossip = RGossip2.client({
|
|
45
|
+
:initial_nodes => options[:initial_nodes],
|
|
46
|
+
:address => @address,
|
|
47
|
+
:data => datas,
|
|
48
|
+
:auth_key => options[:auth_key],
|
|
49
|
+
:port => options[:gossip_port],
|
|
50
|
+
:node_lifetime => options[:gossip_node_lifetime],
|
|
51
|
+
:gossip_interval => options[:gossip_send_interval],
|
|
52
|
+
:receive_timeout => options[:gossip_receive_timeout],
|
|
53
|
+
:logger => @logger,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
# ノードの更新をフック
|
|
57
|
+
@gossip.context.callback_handler = lambda do |act, addr, ts, dt, old_dt|
|
|
58
|
+
case act
|
|
59
|
+
when :add, :comeback
|
|
60
|
+
# 追加・復帰の時は無条件に更新
|
|
61
|
+
update(addr, dt)
|
|
62
|
+
when :update
|
|
63
|
+
# 更新の時はデータが更新されたときのみ更新
|
|
64
|
+
update(addr, dt) if dt != old_dt
|
|
65
|
+
when :delete
|
|
66
|
+
delete(addr)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ヘルスチェック
|
|
71
|
+
@health_checkers = {}
|
|
72
|
+
|
|
73
|
+
if options.config_file and options.config_file['health-check']
|
|
74
|
+
health_check = options.config_file['health-check']
|
|
75
|
+
|
|
76
|
+
if health_check.kind_of?(Hash)
|
|
77
|
+
health_check.each do |name, conf|
|
|
78
|
+
name = name.downcase
|
|
79
|
+
|
|
80
|
+
if datas.any? {|i| i[0] == name }
|
|
81
|
+
checker = HealthChecker.new(name, self, @logger, conf)
|
|
82
|
+
@health_checkers[name] = checker
|
|
83
|
+
# ヘルスチェックはまだ起動しない
|
|
84
|
+
else
|
|
85
|
+
# ホスト名になかったら警告
|
|
86
|
+
@logger.warn("host for a health check is not found: #{name}")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
@logger.warn('configuration of a health check is not right')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# キャッシュ
|
|
95
|
+
@cache = {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Control of service
|
|
99
|
+
def_delegators :@gossip, :stop, :clear_dead_list
|
|
100
|
+
|
|
101
|
+
def start
|
|
102
|
+
# デーモン化すると子プロセスはすぐ死ぬので
|
|
103
|
+
# このタイミングでヘルスチェックを起動
|
|
104
|
+
@health_checkers.each do |name, checker|
|
|
105
|
+
checker.start
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@gossip.start
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def to_hash
|
|
112
|
+
keys = {
|
|
113
|
+
:auth_key => 'auth-key',
|
|
114
|
+
:dns_address => 'address',
|
|
115
|
+
:dns_port => 'port',
|
|
116
|
+
:initial_nodes => lambda {|v| ['initial-nodes', v.join(',')] },
|
|
117
|
+
:resolver => lambda {|v| [
|
|
118
|
+
'resolver',
|
|
119
|
+
v.instance_variable_get(:@config).instance_variable_get(:@config_info)[:nameserver].join(',')
|
|
120
|
+
]},
|
|
121
|
+
:socket => 'socket',
|
|
122
|
+
:max_ip_num => 'max-ip-num',
|
|
123
|
+
:domain => 'domain',
|
|
124
|
+
:log_path => 'log-path',
|
|
125
|
+
:log_level => 'log-level',
|
|
126
|
+
:gossip_port => 'gossip-port',
|
|
127
|
+
:gossip_node_lifetime => lambda {|v| [
|
|
128
|
+
'gossip-node-lifetime',
|
|
129
|
+
@gossip.context.node_lifetime
|
|
130
|
+
]},
|
|
131
|
+
:gossip_send_interval => lambda {|v| [
|
|
132
|
+
'gossip-send-interval',
|
|
133
|
+
@gossip.context.gossip_interval
|
|
134
|
+
]},
|
|
135
|
+
:gossip_receive_timeout => lambda {|v| [
|
|
136
|
+
'gossip-receive-timeout',
|
|
137
|
+
@gossip.context.receive_timeout
|
|
138
|
+
]},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
hash = {}
|
|
142
|
+
|
|
143
|
+
keys.each do |k, name|
|
|
144
|
+
value = @options[k]
|
|
145
|
+
|
|
146
|
+
if value and name.respond_to?(:call)
|
|
147
|
+
name, value = name.call(value)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if value and (not value.kind_of?(String) or not value.empty?)
|
|
151
|
+
hash[name] = value
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
records = list_records
|
|
156
|
+
|
|
157
|
+
hash['host'] = records.find {|r| r[3] == ORIGIN }[0..2].join(',')
|
|
158
|
+
|
|
159
|
+
aliases = records.select {|r| r[3] != ORIGIN }.map do |r|
|
|
160
|
+
[r[1], r[2], (r[3] == MASTER ? 'master' : 'backup')].join(',')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
hash['alias'] = aliases unless aliases.empty?
|
|
164
|
+
|
|
165
|
+
if @options.config_file and @options.config_file['health-check']
|
|
166
|
+
hash['health-check'] = @options.config_file['health-check']
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return hash
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def list_records
|
|
173
|
+
columns = %w(ip_address name ttl priority activity)
|
|
174
|
+
|
|
175
|
+
@db.execute(<<-EOS).map {|i| i.values_at(*columns) }
|
|
176
|
+
SELECT #{columns.join(', ')} FROM records ORDER BY ip_address, name
|
|
177
|
+
EOS
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def add_or_rplace_records(records)
|
|
181
|
+
errmsg = nil
|
|
182
|
+
|
|
183
|
+
# 名前は小文字に変換
|
|
184
|
+
records = records.map do |i|
|
|
185
|
+
name, ttl, priority = i
|
|
186
|
+
name = name.downcase
|
|
187
|
+
[name, ttl, priority]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
@gossip.transaction do
|
|
191
|
+
|
|
192
|
+
# 既存のホスト名は削除
|
|
193
|
+
@gossip.data.reject! do |d|
|
|
194
|
+
if records.any? {|r| r[0] == d[0] }
|
|
195
|
+
# オリジンのPriorityは変更不可
|
|
196
|
+
if d[2] == ORIGIN
|
|
197
|
+
records.each {|r| r[2] = ORIGIN if r[0] == d[0] }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
true
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# データを更新
|
|
205
|
+
records = records.map {|r| r + [ACTIVE] }
|
|
206
|
+
@gossip.data.concat(records)
|
|
207
|
+
end # transaction
|
|
208
|
+
|
|
209
|
+
# データベースを更新
|
|
210
|
+
update(@address, records, true)
|
|
211
|
+
|
|
212
|
+
# ヘルスチェックがあれば開始
|
|
213
|
+
records.map {|i| i.first }.each do |name|
|
|
214
|
+
checker = @health_checkers[name]
|
|
215
|
+
|
|
216
|
+
if checker and not checker.alive?
|
|
217
|
+
checker.start
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
return [!errmsg, errmsg]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def delete_records(names)
|
|
225
|
+
errmsg = nil
|
|
226
|
+
|
|
227
|
+
# 名前は小文字に変換
|
|
228
|
+
names = names.map {|i| i.downcase }
|
|
229
|
+
|
|
230
|
+
@gossip.transaction do
|
|
231
|
+
# データを削除
|
|
232
|
+
@gossip.data.reject! do |d|
|
|
233
|
+
if names.any? {|n| n == d[0] }
|
|
234
|
+
if d[2] == ORIGIN
|
|
235
|
+
# オリジンは削除不可
|
|
236
|
+
errmsg = 'original host name cannot be deleted'
|
|
237
|
+
names.reject! {|n| n == d[0] }
|
|
238
|
+
false
|
|
239
|
+
else
|
|
240
|
+
true
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end # transaction
|
|
245
|
+
|
|
246
|
+
# データベースを更新
|
|
247
|
+
delete_by_names(@address, names)
|
|
248
|
+
|
|
249
|
+
# ヘルスチェックがあれば停止
|
|
250
|
+
names.each do |name|
|
|
251
|
+
checker = @health_checkers[name]
|
|
252
|
+
checker.stop if checker
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
return [!errmsg, errmsg]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def add_nodes(nodes)
|
|
259
|
+
errmsg = nil
|
|
260
|
+
|
|
261
|
+
nodes.each do |i|
|
|
262
|
+
@gossip.add_node(i)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
return [!errmsg, errmsg]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def delete_nodes(nodes)
|
|
269
|
+
errmsg = nil
|
|
270
|
+
|
|
271
|
+
nodes.each do |i|
|
|
272
|
+
@gossip.delete_node(i)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
return [!errmsg, errmsg]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def get_attr(name)
|
|
279
|
+
return unless ATTRIBUTES.has_key?(name)
|
|
280
|
+
|
|
281
|
+
if name == :log_level
|
|
282
|
+
if @gossip.logger
|
|
283
|
+
%w(debug info warn error fatal)[@gossip.logger.level]
|
|
284
|
+
else
|
|
285
|
+
nil
|
|
286
|
+
end
|
|
287
|
+
else
|
|
288
|
+
attr, conv = ATTRIBUTES[name]
|
|
289
|
+
@gossip.context.send(attr).to_s
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def set_attr(name, value)
|
|
294
|
+
return unless ATTRIBUTES.has_key?(name)
|
|
295
|
+
|
|
296
|
+
errmsg = nil
|
|
297
|
+
|
|
298
|
+
if name == :log_level
|
|
299
|
+
if @gossip.logger
|
|
300
|
+
@gossip.logger.level = %w(debug info warn error fatal).index(value.to_s)
|
|
301
|
+
end
|
|
302
|
+
else
|
|
303
|
+
attr, conv = ATTRIBUTES[name]
|
|
304
|
+
@gossip.context.send("#{attr}=", value.send(conv)).to_s
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
return [!errmsg, errmsg]
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def close
|
|
311
|
+
# データベースをクローズ
|
|
312
|
+
@db.close
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Operation of storage
|
|
316
|
+
|
|
317
|
+
def update(address, datas, update_only = false)
|
|
318
|
+
return unless datas
|
|
319
|
+
|
|
320
|
+
datas.each do |i|
|
|
321
|
+
name, ttl, priority, activity = i
|
|
322
|
+
|
|
323
|
+
# 名前は小文字に変換
|
|
324
|
+
name = name.downcase
|
|
325
|
+
|
|
326
|
+
# ホスト名と同じなら警告
|
|
327
|
+
if name == @hostname
|
|
328
|
+
@logger.warn('same hostname as origin was found')
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
@db.execute(<<-EOS, address, name, ttl, priority, activity)
|
|
332
|
+
REPLACE INTO records (ip_address, name, ttl, priority, activity)
|
|
333
|
+
VALUES (?, ?, ?, ?, ?)
|
|
334
|
+
EOS
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# データにないレコードは消す
|
|
338
|
+
unless update_only
|
|
339
|
+
names = datas.map {|i| "'#{i.first.downcase}'" }.join(',')
|
|
340
|
+
|
|
341
|
+
@db.execute(<<-EOS, address)
|
|
342
|
+
DELETE FROM records
|
|
343
|
+
WHERE ip_address = ? AND name NOT IN (#{names})
|
|
344
|
+
EOS
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def delete(address)
|
|
349
|
+
@db.execute('DELETE FROM records WHERE ip_address = ?', address)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def delete_by_names(address, names)
|
|
353
|
+
names = names.map {|i| "'#{i.downcase}'" }.join(',')
|
|
354
|
+
|
|
355
|
+
@db.execute(<<-EOS, address)
|
|
356
|
+
DELETE FROM records
|
|
357
|
+
WHERE ip_address = ? AND name IN (#{names})
|
|
358
|
+
EOS
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Search of records
|
|
362
|
+
|
|
363
|
+
def address_exist?(name)
|
|
364
|
+
# 名前は小文字に変換
|
|
365
|
+
name = name.downcase
|
|
366
|
+
|
|
367
|
+
# ドメインが指定されていたら削除
|
|
368
|
+
name.sub!(/\.#{Regexp.escape(@options[:domain])}\Z/i, '') if @options[:domain]
|
|
369
|
+
|
|
370
|
+
# キャッシュを検索
|
|
371
|
+
@address_records, cache_time = @cache[name]
|
|
372
|
+
now = Time.now
|
|
373
|
+
|
|
374
|
+
unless @address_records and now <= cache_time
|
|
375
|
+
# キャッシュがない場合
|
|
376
|
+
|
|
377
|
+
# シングルスレッドェ…
|
|
378
|
+
@address_records = @db.execute(<<-EOS, name, ACTIVE)
|
|
379
|
+
SELECT ip_address, ttl, priority FROM records
|
|
380
|
+
WHERE name = ? AND activity = ?
|
|
381
|
+
EOS
|
|
382
|
+
|
|
383
|
+
# レコードがあればキャッシュに入れる
|
|
384
|
+
if @address_records.empty?
|
|
385
|
+
# ネガティブキャッシュはしない
|
|
386
|
+
@cache.delete(name)
|
|
387
|
+
else
|
|
388
|
+
min_ttl = nil
|
|
389
|
+
|
|
390
|
+
# レコードはハッシュに変換する
|
|
391
|
+
cache_records = @address_records.map do |i|
|
|
392
|
+
ip_address, ttl, priority = i.values_at('ip_address', 'ttl', 'priority')
|
|
393
|
+
|
|
394
|
+
if min_ttl.nil? or ttl < min_ttl
|
|
395
|
+
min_ttl = ttl
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
{'ip_address' => ip_address, 'ttl' => ttl, 'priority' => priority}
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
expire_time = now + min_ttl
|
|
402
|
+
@cache[name] = [cache_records, expire_time]
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
@address_records.length.nonzero?
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def lookup_addresses(name)
|
|
410
|
+
records = nil
|
|
411
|
+
|
|
412
|
+
#if @address_records.length == 1
|
|
413
|
+
# # レコードが一件ならそれを返す
|
|
414
|
+
# return @address_records.map {|i| i.values_at('ip_address', 'ttl') }
|
|
415
|
+
#end
|
|
416
|
+
@address_record
|
|
417
|
+
|
|
418
|
+
# 優先度の高いレコードを検索
|
|
419
|
+
#records = @address_records #.select {|i| i['priority'] == MASTER }
|
|
420
|
+
|
|
421
|
+
# 次に優先度の高いレコードを検索
|
|
422
|
+
#records.concat(shuffle_records(@address_records.select {|i| i['priority'] == SECONDARY }))
|
|
423
|
+
|
|
424
|
+
# レコードが見つからなかった場合はバックアップを選択
|
|
425
|
+
#if records.empty?
|
|
426
|
+
# records = shuffle_records(@address_records.select {|i| i['priority'] == BACKUP })
|
|
427
|
+
#end
|
|
428
|
+
|
|
429
|
+
# それでもレコードが見つからなかった場合はオリジンを選択
|
|
430
|
+
# ※このパスは通らない
|
|
431
|
+
#records = @address_records if records.empty?
|
|
432
|
+
|
|
433
|
+
# IPアドレス、TTLを返す
|
|
434
|
+
#records.map {|i| i.values_at('ip_address', 'ttl') }
|
|
435
|
+
ensure
|
|
436
|
+
# エラー検出のため、一応クリア
|
|
437
|
+
@address_records = nil
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def name_exist?(address)
|
|
441
|
+
address = x_ip_addr(address)
|
|
442
|
+
|
|
443
|
+
# シングルスレッドェ…
|
|
444
|
+
@name_records = @db.execute(<<-EOS, address, ACTIVE)
|
|
445
|
+
SELECT name, ttl, priority FROM records
|
|
446
|
+
WHERE ip_address = ? AND activity = ?
|
|
447
|
+
EOS
|
|
448
|
+
|
|
449
|
+
@name_records.length.nonzero?
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def lookup_name(address)
|
|
453
|
+
record = nil
|
|
454
|
+
|
|
455
|
+
if @name_records.length == 1
|
|
456
|
+
# レコードが一件ならそれを返す
|
|
457
|
+
record = @name_records.first
|
|
458
|
+
else
|
|
459
|
+
# オリジンを検索
|
|
460
|
+
record = @name_records.find {|i| i['priority'] == ORIGIN }
|
|
461
|
+
|
|
462
|
+
# レコードが見つからなかった場合は優先度の高いレコード選択
|
|
463
|
+
unless record
|
|
464
|
+
record = @name_records.find {|i| i['priority'] == ACTIVE }
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# それでもレコードが見つからなかった場合は優先度の低いレコードを選択
|
|
468
|
+
record = @name_records.first unless record
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# ホスト名、TTLを返す
|
|
472
|
+
return record.values_at('name', 'ttl')
|
|
473
|
+
ensure
|
|
474
|
+
# エラー検出のため、一応クリア
|
|
475
|
+
@name_records = nil
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
private
|
|
479
|
+
|
|
480
|
+
# 乱数でレコードをシャッフルする
|
|
481
|
+
def shuffle_records(records)
|
|
482
|
+
# レコードが一件の時はそのまま返す
|
|
483
|
+
return records if records.length == 1
|
|
484
|
+
|
|
485
|
+
# 先頭のAレコードを決定
|
|
486
|
+
max_ip_num = [records.length, @options[:max_ip_num]].min
|
|
487
|
+
first_index = rand(records.length)
|
|
488
|
+
|
|
489
|
+
# Aレコードを返す
|
|
490
|
+
(records + records).slice(first_index, max_ip_num)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# リソースレコードのデータベース作成
|
|
494
|
+
# もう少し並列処理に強いストレージに変えたいが…
|
|
495
|
+
def create_database
|
|
496
|
+
@db = SQLite3::Database.new(':memory:')
|
|
497
|
+
@db.type_translation = true
|
|
498
|
+
@db.results_as_hash = true
|
|
499
|
+
|
|
500
|
+
# リソースレコード用のテーブル
|
|
501
|
+
# (Typeは必要?)
|
|
502
|
+
@db.execute(<<-EOS)
|
|
503
|
+
CREATE TABLE records (
|
|
504
|
+
ip_address TEXT NOT NULL,
|
|
505
|
+
name TEXT NOT NULL,
|
|
506
|
+
ttl INTEGER NOT NULL,
|
|
507
|
+
priority INTEGER NOT NULL, /* MASTER:1, BACKUP:0, ORIGIN:-1 */
|
|
508
|
+
activity INTEGER NOT NULL, /* Active:1, Inactive:0 */
|
|
509
|
+
PRIMARY KEY (ip_address, name)
|
|
510
|
+
)
|
|
511
|
+
EOS
|
|
512
|
+
|
|
513
|
+
# インデックスを作成(必要?)
|
|
514
|
+
@db.execute(<<-EOS)
|
|
515
|
+
CREATE INDEX idx_name_act
|
|
516
|
+
ON records (name, activity)
|
|
517
|
+
EOS
|
|
518
|
+
|
|
519
|
+
@db.execute(<<-EOS)
|
|
520
|
+
CREATE INDEX idx_ip_act
|
|
521
|
+
ON records (ip_address, activity)
|
|
522
|
+
EOS
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# 逆引き名の変換
|
|
526
|
+
def x_ip_addr(name)
|
|
527
|
+
name.sub(/\.in-addr\.arpa\Z/, '').split('.').reverse.join('.')
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
end # Cloud
|
|
531
|
+
|
|
532
|
+
end # Murakumo
|
data/lib/srv/murakumo_server.rb
CHANGED
|
@@ -69,10 +69,24 @@ module Murakumo
|
|
|
69
69
|
match(@@cloud.method(:address_exist?), :A) do |transaction|
|
|
70
70
|
records = @@cloud.lookup_addresses(transaction.name)
|
|
71
71
|
|
|
72
|
+
addrs = []
|
|
73
|
+
min_ttl = nil # 最小のTTLをセット
|
|
74
|
+
|
|
72
75
|
records.each do |r|
|
|
73
76
|
address, ttl = r
|
|
74
|
-
|
|
77
|
+
|
|
78
|
+
if min_ttl.nil? or ttl < min_ttl
|
|
79
|
+
min_ttl = ttl
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
addrs << Resolv::DNS::Resource::IN::A.new(address)
|
|
75
83
|
end
|
|
84
|
+
|
|
85
|
+
# 直接引数に渡せないので…
|
|
86
|
+
addrs << {:ttl => min_ttl}
|
|
87
|
+
|
|
88
|
+
# スループットをあげるためrespond!は呼ばない
|
|
89
|
+
transaction.append!(*addrs)
|
|
76
90
|
end # match
|
|
77
91
|
|
|
78
92
|
# look up PTR record
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'drb/drb'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'rexec'
|
|
4
|
+
require 'rexec/daemon'
|
|
5
|
+
require 'rubydns'
|
|
6
|
+
require 'socket'
|
|
7
|
+
|
|
8
|
+
require 'srv/murakumo_cloud'
|
|
9
|
+
|
|
10
|
+
BasicSocket.do_not_reverse_lookup = true
|
|
11
|
+
|
|
12
|
+
module Murakumo
|
|
13
|
+
|
|
14
|
+
# RExecに依存しているのでこんな設計に…
|
|
15
|
+
class Server < RExec::Daemon::Base
|
|
16
|
+
|
|
17
|
+
# クラスメソッドを定義
|
|
18
|
+
class << self
|
|
19
|
+
|
|
20
|
+
def init(options)
|
|
21
|
+
# クラスインスタンス変数を使わないこと
|
|
22
|
+
@@options = options
|
|
23
|
+
@@cloud = Cloud.new(options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def pid_directory=(path)
|
|
27
|
+
@@pid_directory = path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run
|
|
31
|
+
RubyDNS.run_server(:listen => [[:udp, @@options[:dns_address], @@options[:dns_port]]]) do
|
|
32
|
+
# RubyDNS::Serverのコンテキスト
|
|
33
|
+
@logger = @@options[:logger] if @@options[:logger]
|
|
34
|
+
|
|
35
|
+
on(:start) do
|
|
36
|
+
if @@options[:socket]
|
|
37
|
+
# 既存のソケットファイルは削除
|
|
38
|
+
FileUtils.rm_f(@@options[:socket])
|
|
39
|
+
|
|
40
|
+
# ServerクラスをDRuby化
|
|
41
|
+
DRb.start_service("drbunix:#{@@options[:socket]}", @@cloud)
|
|
42
|
+
at_exit { FileUtils.rm_f(@@options[:socket]) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# HUPでログをローテート
|
|
46
|
+
if @@options[:logger]
|
|
47
|
+
trap(:HUP) do
|
|
48
|
+
if logger = @@options[:logger]
|
|
49
|
+
logdev = logger.instance_variable_get(:@logdev)
|
|
50
|
+
|
|
51
|
+
if (dev = logdev.dev).kind_of?(File)
|
|
52
|
+
path = dev.path
|
|
53
|
+
mutex = logdev.instance_variable_get(:@mutex)
|
|
54
|
+
|
|
55
|
+
mutex.synchronize do
|
|
56
|
+
dev.reopen(path, 'a')
|
|
57
|
+
dev.sync = true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# ゴシッププロトコルを開始
|
|
65
|
+
@@cloud.start
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# look up A record
|
|
69
|
+
match(@@cloud.method(:address_exist?), :A) do |transaction|
|
|
70
|
+
transaction.append!(Resolv::DNS::Resource::IN::A.new('10.158.59.165'), Resolv::DNS::Resource::IN::A.new('10.148.150.17'))
|
|
71
|
+
#records = @@cloud.lookup_addresses(transaction.name)
|
|
72
|
+
|
|
73
|
+
#records.each do |r|
|
|
74
|
+
# address, ttl = r.values_at('ip_address', 'ttl')
|
|
75
|
+
# transaction.respond!(address, :ttl => ttl)
|
|
76
|
+
#end
|
|
77
|
+
end # match
|
|
78
|
+
|
|
79
|
+
# look up PTR record
|
|
80
|
+
match(@@cloud.method(:name_exist?), :PTR) do |transaction|
|
|
81
|
+
name, ttl = @@cloud.lookup_name(transaction.name)
|
|
82
|
+
transaction.respond!(Resolv::DNS::Name.create("#{name}."), :ttl => ttl)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if @@options[:resolver]
|
|
86
|
+
otherwise do |transaction|
|
|
87
|
+
transaction.passthrough!(@@options[:resolver])
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end # RubyDNS.run_server
|
|
91
|
+
end # run
|
|
92
|
+
|
|
93
|
+
def shutdown
|
|
94
|
+
@@cloud.stop
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end # class << self
|
|
98
|
+
|
|
99
|
+
end # Server
|
|
100
|
+
|
|
101
|
+
end # Murakumo
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: murakumo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 19
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
version: 0.
|
|
8
|
+
- 3
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.3.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- winebarrel
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-11-
|
|
18
|
+
date: 2011-11-23 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: rubydns
|
|
@@ -93,23 +93,29 @@ extra_rdoc_files: []
|
|
|
93
93
|
|
|
94
94
|
files:
|
|
95
95
|
- README
|
|
96
|
+
- bin/murakumo.orig
|
|
97
|
+
- bin/murakumo
|
|
98
|
+
- bin/murakumo-install-init-script.orig
|
|
96
99
|
- bin/murakumo-install-init-script
|
|
100
|
+
- bin/mrkmctl.orig
|
|
97
101
|
- bin/mrkmctl
|
|
98
|
-
- bin/murakumo
|
|
99
|
-
- lib/cli/murakumo_options.rb
|
|
100
102
|
- lib/cli/mrkmctl.rb
|
|
101
|
-
- lib/cli/mrkmctl_options.rb
|
|
102
103
|
- lib/cli/murakumo.rb
|
|
103
|
-
- lib/
|
|
104
|
+
- lib/cli/murakumo_options.rb
|
|
105
|
+
- lib/cli/mrkmctl_options.rb
|
|
106
|
+
- lib/misc/murakumo_const.rb
|
|
104
107
|
- lib/srv/murakumo_server.rb
|
|
108
|
+
- lib/srv/murakumo_cloud.rb.orig
|
|
109
|
+
- lib/srv/murakumo_health_checker_context.rb
|
|
110
|
+
- lib/srv/murakumo_server.rb.orig
|
|
105
111
|
- lib/srv/murakumo_health_checker.rb
|
|
106
112
|
- lib/srv/murakumo_cloud.rb
|
|
107
|
-
-
|
|
108
|
-
- etc/murakumo-update-config-for-ec2
|
|
109
|
-
- etc/gai.conf.example
|
|
113
|
+
- etc/murakumo.server
|
|
114
|
+
- etc/murakumo-update-config-for-ec2.orig
|
|
110
115
|
- etc/murakumo.yml.example
|
|
116
|
+
- etc/gai.conf.example
|
|
117
|
+
- etc/murakumo-update-config-for-ec2
|
|
111
118
|
- etc/dhclient-script.patch
|
|
112
|
-
- etc/murakumo.server
|
|
113
119
|
homepage: https://bitbucket.org/winebarrel/murakumo
|
|
114
120
|
licenses: []
|
|
115
121
|
|
|
@@ -139,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
139
145
|
requirements: []
|
|
140
146
|
|
|
141
147
|
rubyforge_project:
|
|
142
|
-
rubygems_version: 1.8.
|
|
148
|
+
rubygems_version: 1.8.11
|
|
143
149
|
signing_key:
|
|
144
150
|
specification_version: 3
|
|
145
151
|
summary: Murakumo is the internal DNS server which manages name information using a gossip protocol.
|