murakumo 0.3.0 → 0.3.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/lib/misc/murakumo_const.rb +1 -1
- data/lib/srv/murakumo_health_checker_context.rb +7 -4
- metadata +3 -9
- data/bin/mrkmctl.orig +0 -5
- data/bin/murakumo-install-init-script.orig +0 -34
- data/bin/murakumo.orig +0 -5
- data/etc/murakumo-update-config-for-ec2.orig +0 -33
- data/lib/srv/murakumo_cloud.rb.orig +0 -532
- data/lib/srv/murakumo_server.rb.orig +0 -101
data/lib/misc/murakumo_const.rb
CHANGED
@@ -19,18 +19,21 @@ module Murakumo
|
|
19
19
|
s = TCPSocket.new(host, port)
|
20
20
|
s.close
|
21
21
|
return true
|
22
|
-
rescue
|
22
|
+
rescue => e
|
23
|
+
@logger.debug("#{@name}: #{e.message}")
|
23
24
|
return false
|
24
25
|
end
|
25
26
|
|
26
27
|
# HTTPチェッカー
|
27
28
|
def http_get(path, statuses = [200], host = '127.0.0.1', port = 80)
|
28
29
|
res = Net::HTTP.start('127.0.0.1', 80) do |http|
|
30
|
+
http.read_timeout = @options['timeout']
|
29
31
|
http.get(path)
|
30
32
|
end
|
31
33
|
|
32
34
|
statuses.include?(res.code.to_i)
|
33
|
-
rescue
|
35
|
+
rescue => e
|
36
|
+
@logger.debug("#{@name}: #{e.message}")
|
34
37
|
return false
|
35
38
|
end
|
36
39
|
|
@@ -53,7 +56,7 @@ module Murakumo
|
|
53
56
|
my = Mysql.new(host, user, passwd, db, port, sock)
|
54
57
|
!!(my.respond_to?(:ping) ? my.ping : my.stat)
|
55
58
|
rescue => e
|
56
|
-
@logger.debug(e.message)
|
59
|
+
@logger.debug("#{@name}: #{e.message}")
|
57
60
|
return false
|
58
61
|
ensure
|
59
62
|
my.close if my
|
@@ -80,7 +83,7 @@ module Murakumo
|
|
80
83
|
my = Mysql2::Client.new(opts)
|
81
84
|
my.ping
|
82
85
|
rescue => e
|
83
|
-
@logger.debug(e.message)
|
86
|
+
@logger.debug("#{@name}: #{e.message}")
|
84
87
|
return false
|
85
88
|
ensure
|
86
89
|
my.close if my
|
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: 17
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
9
|
+
- 1
|
10
|
+
version: 0.3.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- winebarrel
|
@@ -93,11 +93,8 @@ extra_rdoc_files: []
|
|
93
93
|
|
94
94
|
files:
|
95
95
|
- README
|
96
|
-
- bin/murakumo.orig
|
97
96
|
- bin/murakumo
|
98
|
-
- bin/murakumo-install-init-script.orig
|
99
97
|
- bin/murakumo-install-init-script
|
100
|
-
- bin/mrkmctl.orig
|
101
98
|
- bin/mrkmctl
|
102
99
|
- lib/cli/mrkmctl.rb
|
103
100
|
- lib/cli/murakumo.rb
|
@@ -105,13 +102,10 @@ files:
|
|
105
102
|
- lib/cli/mrkmctl_options.rb
|
106
103
|
- lib/misc/murakumo_const.rb
|
107
104
|
- lib/srv/murakumo_server.rb
|
108
|
-
- lib/srv/murakumo_cloud.rb.orig
|
109
105
|
- lib/srv/murakumo_health_checker_context.rb
|
110
|
-
- lib/srv/murakumo_server.rb.orig
|
111
106
|
- lib/srv/murakumo_health_checker.rb
|
112
107
|
- lib/srv/murakumo_cloud.rb
|
113
108
|
- etc/murakumo.server
|
114
|
-
- etc/murakumo-update-config-for-ec2.orig
|
115
109
|
- etc/murakumo.yml.example
|
116
110
|
- etc/gai.conf.example
|
117
111
|
- etc/murakumo-update-config-for-ec2
|
data/bin/mrkmctl.orig
DELETED
@@ -1,34 +0,0 @@
|
|
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
DELETED
@@ -1,33 +0,0 @@
|
|
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
|
@@ -1,532 +0,0 @@
|
|
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
|
@@ -1,101 +0,0 @@
|
|
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
|