murakumo 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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 dev-01
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,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'cli/mrkmctl'
@@ -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,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'cli/murakumo'
@@ -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
@@ -70,6 +70,7 @@ max-ip-num: 8
70
70
 
71
71
  #initial-nodes: 10.11.12.14, 10.11.12.15
72
72
  #domain: ap-northeast-1.compute.internal
73
+ #enable-cache: true
73
74
 
74
75
  host: $ip_addr, $hostname, 60
75
76
 
@@ -10,6 +10,7 @@ max-ip-num: 8
10
10
 
11
11
  #initial-nodes: 10.11.12.14, 10.11.12.15
12
12
  #domain: ap-northeast-1.compute.internal
13
+ #enable-cache: true
13
14
 
14
15
  # Ip address, hostname, ttl
15
16
  host: 10.11.12.13, my-host, 60
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  module Murakumo
2
- VERSION = '0.2.9'
2
+ VERSION = '0.3.0'
3
3
 
4
4
  # Priority
5
5
  MASTER = 1
@@ -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
- @address_records = @db.execute(<<-EOS, name, ACTIVE)
367
- SELECT ip_address, ttl, priority FROM records
368
- WHERE name = ? AND activity = ?
369
- EOS
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
@@ -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
- transaction.respond!(address, :ttl => ttl)
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: 5
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 9
10
- version: 0.2.9
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-22 00:00:00 Z
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/srv/murakumo_health_checker_context.rb
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
- - lib/misc/murakumo_const.rb
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.1
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.