murakumo 0.1.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 +57 -0
- data/bin/mrkmctl +5 -0
- data/bin/murakumo +5 -0
- data/etc/murakumo.server +95 -0
- data/etc/murakumo.yml.sample +33 -0
- data/lib/cli/mrkmctl.rb +74 -0
- data/lib/cli/mrkmctl_options.rb +122 -0
- data/lib/cli/murakumo.rb +20 -0
- data/lib/cli/murakumo_options.rb +158 -0
- data/lib/misc/murakumo_const.rb +17 -0
- data/lib/srv/murakumo_cloud.rb +457 -0
- data/lib/srv/murakumo_health_checker.rb +134 -0
- data/lib/srv/murakumo_health_checker_context.rb +33 -0
- data/lib/srv/murakumo_server.rb +102 -0
- metadata +142 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'resolv-replace'
|
3
|
+
|
4
|
+
require 'srv/murakumo_health_checker_context'
|
5
|
+
require 'misc/murakumo_const'
|
6
|
+
|
7
|
+
module Murakumo
|
8
|
+
|
9
|
+
class HealthChecker
|
10
|
+
|
11
|
+
def initialize(name, cloud, logger, options)
|
12
|
+
# 名前は小文字に変換
|
13
|
+
@name = name.downcase
|
14
|
+
@cloud = cloud
|
15
|
+
@logger = logger
|
16
|
+
|
17
|
+
# 各種変数の設定
|
18
|
+
{
|
19
|
+
'interval' => [30, 1, 300],
|
20
|
+
'timeout' => [ 5, 1, 300],
|
21
|
+
'healthy' => [10, 2, 10],
|
22
|
+
'unhealthy' => [ 2, 2, 10],
|
23
|
+
}.each {|key, vals|
|
24
|
+
defval, min, max = vals
|
25
|
+
value = (options[key] || defval).to_i
|
26
|
+
value = min if value < min
|
27
|
+
value = max if value > max
|
28
|
+
instance_variable_set("@#{key}", value)
|
29
|
+
}
|
30
|
+
|
31
|
+
# スクリプトの読み込み
|
32
|
+
@script = options['script']
|
33
|
+
raise "health check script of #{@name} is not found" unless @script
|
34
|
+
@script = File.read(script) if File.exists?(@script)
|
35
|
+
end
|
36
|
+
|
37
|
+
def good
|
38
|
+
if @normal_health
|
39
|
+
@unhealthy_count = 0
|
40
|
+
elsif (@healthy_count += 1) >= @healthy
|
41
|
+
toggle_health
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def bad
|
46
|
+
if not @normal_health
|
47
|
+
@healthy_count = 0
|
48
|
+
elsif (@unhealthy_count += 1) >= @unhealthy
|
49
|
+
toggle_health
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def toggle_health
|
54
|
+
@normal_health = !@normal_health
|
55
|
+
activity = (@normal_health ? ACTIVE : INACTIVE)
|
56
|
+
|
57
|
+
@cloud.gossip.transaction do
|
58
|
+
@cloud.gossip.data.each do |i|
|
59
|
+
# 名前の一致するデータを更新
|
60
|
+
i[3] = activity if i[0] == @name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@cloud.db.execute(<<-EOS, activity, @cloud.address, @name)
|
65
|
+
UPDATE records SET activity = ?
|
66
|
+
WHERE ip_address = ? AND name = ?
|
67
|
+
EOS
|
68
|
+
|
69
|
+
@healthy_count = 0
|
70
|
+
@unhealthy_count = 0
|
71
|
+
|
72
|
+
status = @normal_health ? 'healthy' : 'unhealthy'
|
73
|
+
@logger.info("health condition changed: #{@name}: #{status}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def start
|
77
|
+
# 各種変数は初期状態にする
|
78
|
+
@alive = true
|
79
|
+
@normal_health = true
|
80
|
+
@healthy_count = 0
|
81
|
+
@unhealthy_count = 0
|
82
|
+
|
83
|
+
# 既存のスレッドは破棄
|
84
|
+
if @thread and @thread.alive?
|
85
|
+
begin
|
86
|
+
@thread.kill
|
87
|
+
rescue ThreadError
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@thread = Thread.start {
|
92
|
+
healthy = 0
|
93
|
+
unhealthy = 0
|
94
|
+
|
95
|
+
begin
|
96
|
+
while @alive
|
97
|
+
retval = nil
|
98
|
+
|
99
|
+
begin
|
100
|
+
retval = timeout(@timeout) {
|
101
|
+
HealthCheckerContext.new.instance_eval(@script)
|
102
|
+
}
|
103
|
+
rescue Timeout::Error
|
104
|
+
retval = false
|
105
|
+
end
|
106
|
+
|
107
|
+
status = retval == true ? 'good' : retval == false ? 'bad' : '-'
|
108
|
+
@logger.debug("result of a health check: #{@name}: #{status}")
|
109
|
+
|
110
|
+
if retval == true
|
111
|
+
good
|
112
|
+
elsif retval == false
|
113
|
+
bad
|
114
|
+
end
|
115
|
+
|
116
|
+
sleep @interval
|
117
|
+
end # while
|
118
|
+
rescue Exception => e
|
119
|
+
message = (["#{e.class}: #{e.message}"] + (e.backtrace || [])).join("\n\tfrom ")
|
120
|
+
@logger.error("#{@name}: #{message}")
|
121
|
+
end # begin
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def stop
|
126
|
+
@alive = false
|
127
|
+
end
|
128
|
+
|
129
|
+
def alive?
|
130
|
+
@alive
|
131
|
+
end
|
132
|
+
end # HealthChecker
|
133
|
+
|
134
|
+
end # Murakumo
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'misc/murakumo_const'
|
5
|
+
|
6
|
+
module Murakumo
|
7
|
+
|
8
|
+
# ヘルスチェックのコンテキスト
|
9
|
+
class HealthCheckerContext
|
10
|
+
|
11
|
+
# TCPチェッカー
|
12
|
+
def tcp_check(port, host = '127.0.0.1')
|
13
|
+
s = TCPSocket.new(host, port)
|
14
|
+
s.close
|
15
|
+
return true
|
16
|
+
rescue Exception
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
20
|
+
# HTTPチェッカー
|
21
|
+
def http_get(path, statuses = [200], host = '127.0.0.1', port = 80)
|
22
|
+
res = Net::HTTP.start('127.0.0.1', 80) do |http|
|
23
|
+
http.get(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
statuses.include?(res.code.to_i)
|
27
|
+
rescue Exception
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
end # HealthCheckerContext
|
32
|
+
|
33
|
+
end # Murakumo
|
@@ -0,0 +1,102 @@
|
|
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
|
+
# ServerクラスをDRuby化
|
38
|
+
DRb.start_service("drbunix:#{@@options[:socket]}", @@cloud)
|
39
|
+
at_exit { FileUtils.rm_f(@@options[:socket]) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# HUPでログをローテート
|
43
|
+
if @@options[:logger]
|
44
|
+
trap(:HUP) do
|
45
|
+
if logger = @@options[:logger]
|
46
|
+
logdev = logger.instance_variable_get(:@logdev)
|
47
|
+
|
48
|
+
if (dev = logdev.dev).kind_of?(File)
|
49
|
+
path = dev.path
|
50
|
+
mutex = logdev.instance_variable_get(:@mutex)
|
51
|
+
|
52
|
+
mutex.synchronize do
|
53
|
+
dev.reopen(path, 'a')
|
54
|
+
dev.sync = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# ゴシッププロトコルを開始
|
62
|
+
@@cloud.start
|
63
|
+
end
|
64
|
+
|
65
|
+
# look up A record
|
66
|
+
match(@@cloud.method(:address_exist?), :A) do |transaction|
|
67
|
+
records = @@cloud.lookup_addresses(transaction.name)
|
68
|
+
|
69
|
+
# 先頭のAレコードを決定
|
70
|
+
max_ip_num = [records.length, @@options[:max_ip_num]].min
|
71
|
+
first_index = rand(max_ip_num);
|
72
|
+
|
73
|
+
# Aレコードを返す
|
74
|
+
(records + records).slice(first_index, max_ip_num).each do |r|
|
75
|
+
address, ttl = r
|
76
|
+
transaction.respond!(address, :ttl => ttl)
|
77
|
+
end
|
78
|
+
end # match
|
79
|
+
|
80
|
+
# look up PTR record
|
81
|
+
match(@@cloud.method(:name_exist?), :PTR) do |transaction|
|
82
|
+
name, ttl = @@cloud.lookup_name(transaction.name)
|
83
|
+
transaction.respond!(Resolv::DNS::Name.create("#{name}."), :ttl => ttl)
|
84
|
+
end
|
85
|
+
|
86
|
+
if @@options[:resolver]
|
87
|
+
otherwise do |transaction|
|
88
|
+
transaction.passthrough!(@@options[:resolver])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # RubyDNS.run_server
|
92
|
+
end # run
|
93
|
+
|
94
|
+
def shutdown
|
95
|
+
@@cloud.stop
|
96
|
+
end
|
97
|
+
|
98
|
+
end # class << self
|
99
|
+
|
100
|
+
end # Server
|
101
|
+
|
102
|
+
end # Murakumo
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: murakumo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- winebarrel
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-16 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rubydns
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 21
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 3
|
32
|
+
- 3
|
33
|
+
version: 0.3.3
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rgossip2
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 17
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 1
|
48
|
+
- 5
|
49
|
+
version: 0.1.5
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: optopus
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 9
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
- 1
|
64
|
+
- 9
|
65
|
+
version: 0.1.9
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: sqlite3-ruby
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 21
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 2
|
80
|
+
- 5
|
81
|
+
version: 1.2.5
|
82
|
+
type: :runtime
|
83
|
+
version_requirements: *id004
|
84
|
+
description:
|
85
|
+
email: sgwr_dts@yahoo.co.jp
|
86
|
+
executables:
|
87
|
+
- murakumo
|
88
|
+
- mrkmctl
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files: []
|
92
|
+
|
93
|
+
files:
|
94
|
+
- README
|
95
|
+
- bin/mrkmctl
|
96
|
+
- bin/murakumo
|
97
|
+
- lib/cli/murakumo_options.rb
|
98
|
+
- lib/cli/mrkmctl.rb
|
99
|
+
- lib/cli/mrkmctl_options.rb
|
100
|
+
- lib/cli/murakumo.rb
|
101
|
+
- lib/srv/murakumo_health_checker_context.rb
|
102
|
+
- lib/srv/murakumo_server.rb
|
103
|
+
- lib/srv/murakumo_health_checker.rb
|
104
|
+
- lib/srv/murakumo_cloud.rb
|
105
|
+
- lib/misc/murakumo_const.rb
|
106
|
+
- etc/murakumo.yml.sample
|
107
|
+
- etc/murakumo.server
|
108
|
+
homepage: https://bitbucket.org/winebarrel/murakumo
|
109
|
+
licenses: []
|
110
|
+
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
hash: 3
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
version: "0"
|
134
|
+
requirements: []
|
135
|
+
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 1.8.1
|
138
|
+
signing_key:
|
139
|
+
specification_version: 3
|
140
|
+
summary: Murakumo is the internal DNS server which manages name information using a gossip protocol.
|
141
|
+
test_files: []
|
142
|
+
|