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 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.