pylon 0.1.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/.document +5 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +201 -0
- data/NOTICE +8 -0
- data/README.org +3 -0
- data/Rakefile +44 -0
- data/Vagrantfile +19 -0
- data/bin/pylon +33 -0
- data/cookbooks/pylon/README.rdoc +8 -0
- data/cookbooks/pylon/metadata.rb +6 -0
- data/cookbooks/pylon/recipes/default.rb +42 -0
- data/lib/pylon.rb +19 -0
- data/lib/pylon/application.rb +161 -0
- data/lib/pylon/config.rb +61 -0
- data/lib/pylon/daemon.rb +171 -0
- data/lib/pylon/elector.rb +164 -0
- data/lib/pylon/log.rb +24 -0
- data/lib/pylon/node.rb +147 -0
- data/test/helper.rb +19 -0
- data/test/test_boss.rb +7 -0
- data/vendor/cache/archive-tar-minitar-0.5.2.gem +0 -0
- data/vendor/cache/erubis-2.7.0.gem +0 -0
- data/vendor/cache/ffi-1.0.9.gem +0 -0
- data/vendor/cache/ffi-rzmq-0.8.2.gem +0 -0
- data/vendor/cache/git-1.2.5.gem +0 -0
- data/vendor/cache/i18n-0.5.0.gem +0 -0
- data/vendor/cache/jeweler-1.6.4.gem +0 -0
- data/vendor/cache/json-1.5.3.gem +0 -0
- data/vendor/cache/minitest-2.6.0.gem +0 -0
- data/vendor/cache/mixlib-cli-1.2.2.gem +0 -0
- data/vendor/cache/mixlib-config-1.1.2.gem +0 -0
- data/vendor/cache/mixlib-log-1.3.0.gem +0 -0
- data/vendor/cache/net-scp-1.0.4.gem +0 -0
- data/vendor/cache/net-ssh-2.1.4.gem +0 -0
- data/vendor/cache/rake-0.9.2.gem +0 -0
- data/vendor/cache/rcov-0.9.10.gem +0 -0
- data/vendor/cache/thor-0.14.6.gem +0 -0
- data/vendor/cache/uuidtools-2.1.2.gem +0 -0
- data/vendor/cache/vagrant-0.8.2.gem +0 -0
- data/vendor/cache/virtualbox-0.9.1.gem +0 -0
- metadata +227 -0
data/lib/pylon.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
|
2
|
+
# Copyright:: Copyright (c) 2011 AJ Christensen
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or#implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
class Pylon
|
18
|
+
VERSION = "0.1.1"
|
19
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
#
|
2
|
+
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
|
3
|
+
# Copyright:: Copyright (c) 2011 AJ Christensen
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or#implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require "mixlib/cli"
|
20
|
+
require_relative "config"
|
21
|
+
require_relative "log"
|
22
|
+
require_relative "daemon"
|
23
|
+
require_relative "elector"
|
24
|
+
|
25
|
+
class Pylon
|
26
|
+
class Application
|
27
|
+
class << self
|
28
|
+
def fatal! message, error_code = -1
|
29
|
+
Pylon::Log.fatal message
|
30
|
+
Process.exit error_code
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
include Mixlib::CLI
|
36
|
+
|
37
|
+
def configure_logging
|
38
|
+
Pylon::Log.init(Pylon::Config[:log_location])
|
39
|
+
if ( Pylon::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Pylon::Config[:daemonize])
|
40
|
+
stdout_logger = Logger.new(STDOUT)
|
41
|
+
STDOUT.sync = true
|
42
|
+
stdout_logger.formatter = Pylon::Log.logger.formatter
|
43
|
+
Pylon::Log.loggers << stdout_logger
|
44
|
+
end
|
45
|
+
Pylon::Log.level = Pylon::Config[:log_level]
|
46
|
+
end
|
47
|
+
|
48
|
+
def run argv=ARGV
|
49
|
+
parse_options(argv)
|
50
|
+
Pylon::Config.merge!(config)
|
51
|
+
configure_logging
|
52
|
+
Pylon::Log.info "Pylon #{Pylon::VERSION} warming up"
|
53
|
+
|
54
|
+
Pylon::Daemon.change_privilege
|
55
|
+
Pylon::Daemon.daemonize "pylon" if Pylon::Config[:daemonize]
|
56
|
+
|
57
|
+
elector = Pylon::Elector.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
super
|
62
|
+
trap("TERM") do
|
63
|
+
Pylon::Application.fatal!("SIGTERM received, stopping", 1)
|
64
|
+
end
|
65
|
+
|
66
|
+
trap("INT") do
|
67
|
+
Pylon::Application.fatal!("SIGINT received, stopping", 2)
|
68
|
+
end
|
69
|
+
|
70
|
+
unless RUBY_PLATFORM =~ /mswin|mingw32|windows/
|
71
|
+
trap("QUIT") do
|
72
|
+
Pylon::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n "))
|
73
|
+
end
|
74
|
+
|
75
|
+
trap("HUP") do
|
76
|
+
Pylon::Log.info("SIGHUP received, reconfiguring")
|
77
|
+
# reconfigure
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
option :config_file,
|
83
|
+
:short => "-c CONFIG",
|
84
|
+
:long => "--config CONFIG",
|
85
|
+
:default => 'config.rb',
|
86
|
+
:description => "The configuration file to use"
|
87
|
+
|
88
|
+
option :log_level,
|
89
|
+
:short => "-l LEVEL",
|
90
|
+
:long => "--log_level LEVEL",
|
91
|
+
:description => "Set the log level (debug, info, warn, error, fatal)",
|
92
|
+
:required => true,
|
93
|
+
:proc => Proc.new { |l| l.to_sym }
|
94
|
+
|
95
|
+
option :help,
|
96
|
+
:short => "-h",
|
97
|
+
:long => "--help",
|
98
|
+
:description => "Show this message",
|
99
|
+
:on => :tail,
|
100
|
+
:boolean => true,
|
101
|
+
:show_options => true,
|
102
|
+
:exit => 0
|
103
|
+
|
104
|
+
option :daemonize,
|
105
|
+
:short => "-d",
|
106
|
+
:long => "--daemonize",
|
107
|
+
:description => "send pylon to the background",
|
108
|
+
:proc => lambda { |p| true }
|
109
|
+
|
110
|
+
option :user,
|
111
|
+
:short => "-u USER",
|
112
|
+
:long => "--user USER",
|
113
|
+
:description => "User to set privilege to",
|
114
|
+
:proc => nil
|
115
|
+
|
116
|
+
option :group,
|
117
|
+
:short => "-g GROUP",
|
118
|
+
:long => "--group GROUP",
|
119
|
+
:description => "Group to set privilege to",
|
120
|
+
:proc => nil
|
121
|
+
|
122
|
+
option :multicast_address,
|
123
|
+
:short => "-a ADDRESS",
|
124
|
+
:long => "--multicast-address ADDRESS",
|
125
|
+
:description => "Address to use for UDP multicast"
|
126
|
+
|
127
|
+
option :multicast_port,
|
128
|
+
:short => "-p PORT",
|
129
|
+
:long => "--multicast-port PORT",
|
130
|
+
:description => "Port to use for UDP multicast"
|
131
|
+
|
132
|
+
option :multicast_loopback,
|
133
|
+
:short => "-L",
|
134
|
+
:long => "--multicast-loopback",
|
135
|
+
:description => "Enable multicast over loopback interfaces",
|
136
|
+
:proc => lambda { |loop| true }
|
137
|
+
|
138
|
+
option :interface,
|
139
|
+
:short => "-i INTERFACE",
|
140
|
+
:long => "--interface INTERFACE",
|
141
|
+
:description => "Interface to use to send multicast over"
|
142
|
+
|
143
|
+
option :tcp_address,
|
144
|
+
:short => "-t TCPADDRESS",
|
145
|
+
:long => "--tcp-address TCPADDRESS",
|
146
|
+
:description => "Interface to use to bind request socket to"
|
147
|
+
|
148
|
+
option :tcp_port,
|
149
|
+
:short => "-P TCPPORT",
|
150
|
+
:long => "--tcp-port TCPPORT",
|
151
|
+
:description => "Port to bind request socket to"
|
152
|
+
|
153
|
+
option :minimum_master_nodes,
|
154
|
+
:short => "-m NODES",
|
155
|
+
:long => "--minimum-master-nodes NODES",
|
156
|
+
:description => "How many nodes to wait for before starting master election",
|
157
|
+
:proc => lambda { |nodes| nodes.to_i }
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
data/lib/pylon/config.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#
|
2
|
+
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
|
3
|
+
# Copyright:: Copyright (c) 2011 AJ Christensen
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or#implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require "mixlib/config"
|
19
|
+
|
20
|
+
class Pylon
|
21
|
+
class Config
|
22
|
+
extend Mixlib::Config
|
23
|
+
|
24
|
+
config_file "~/.pylon.rb"
|
25
|
+
log_level :info
|
26
|
+
log_location STDOUT
|
27
|
+
daemonize false
|
28
|
+
user nil
|
29
|
+
group nil
|
30
|
+
umask 0022
|
31
|
+
|
32
|
+
# Options for the multicast server
|
33
|
+
multicast true
|
34
|
+
multicast_address "225.4.5.6"
|
35
|
+
multicast_port "13336"
|
36
|
+
multicast_ttl 3
|
37
|
+
multicast_listen_address nil
|
38
|
+
multicast_loopback false
|
39
|
+
interface_address "127.0.0.1"
|
40
|
+
|
41
|
+
# TCP settings
|
42
|
+
tcp_address "*"
|
43
|
+
tcp_port "13335"
|
44
|
+
tcp_retries 10
|
45
|
+
tcp_timeout 5
|
46
|
+
|
47
|
+
# cluster settings
|
48
|
+
maximum_weight 1000
|
49
|
+
cluster_name "pylon"
|
50
|
+
Seed_tcp_endpoints []
|
51
|
+
master nil
|
52
|
+
minimum_master_nodes 1
|
53
|
+
sleep_after_announce 5
|
54
|
+
|
55
|
+
# TODO: not implemented
|
56
|
+
ping_interval 1
|
57
|
+
ping_timeout 30
|
58
|
+
ping_retries 3
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/pylon/daemon.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
#
|
2
|
+
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
|
3
|
+
# Copyright:: Copyright (c) 2011 AJ Christensen
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require "etc"
|
19
|
+
require_relative "application"
|
20
|
+
require_relative "config"
|
21
|
+
|
22
|
+
class Pylon
|
23
|
+
class Daemon
|
24
|
+
class << self
|
25
|
+
attr_accessor :name
|
26
|
+
|
27
|
+
# Daemonize the current process, managing pidfiles and process uid/gid
|
28
|
+
#
|
29
|
+
# === Parameters
|
30
|
+
# name<String>:: The name to be used for the pid file
|
31
|
+
#
|
32
|
+
def daemonize(name)
|
33
|
+
@name = name
|
34
|
+
pid = pid_from_file
|
35
|
+
unless running?
|
36
|
+
remove_pid_file()
|
37
|
+
Pylon::Log.info("Daemonizing..")
|
38
|
+
begin
|
39
|
+
exit if fork
|
40
|
+
Process.setsid
|
41
|
+
exit if fork
|
42
|
+
Pylon::Log.info("Forked, in #{Process.pid}. Priveleges: #{Process.euid} #{Process.egid}")
|
43
|
+
File.umask Pylon::Config[:umask]
|
44
|
+
$stdin.reopen("/dev/null")
|
45
|
+
$stdout.reopen("/dev/null", "a")
|
46
|
+
$stderr.reopen($stdout)
|
47
|
+
save_pid_file
|
48
|
+
at_exit { remove_pid_file }
|
49
|
+
rescue NotImplementedError => e
|
50
|
+
Pylon::Application.fatal!("There is no fork: #{e.message}")
|
51
|
+
end
|
52
|
+
else
|
53
|
+
Pylon::Application.fatal!("Pylon is already running pid #{pid}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if Pylon is running based on the pid_file
|
58
|
+
# ==== Returns
|
59
|
+
# Boolean::
|
60
|
+
# True if Pylon is running
|
61
|
+
# False if Pylon is not running
|
62
|
+
#
|
63
|
+
def running?
|
64
|
+
if pid_from_file.nil?
|
65
|
+
false
|
66
|
+
else
|
67
|
+
Process.kill(0, pid_from_file)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
71
|
+
false
|
72
|
+
rescue Errno::EACCES => e
|
73
|
+
Pylon::Application.fatal!("You don't have access to the PID file at #{pid_file}: #{e.message}")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Gets the pid file for @name
|
77
|
+
# ==== Returns
|
78
|
+
# String::
|
79
|
+
# Location of the pid file for @name
|
80
|
+
def pid_file
|
81
|
+
Pylon::Config[:pid_file] or "/tmp/#{@name}.pid"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Suck the pid out of pid_file
|
85
|
+
# ==== Returns
|
86
|
+
# Integer::
|
87
|
+
# The PID from pid_file
|
88
|
+
# nil::
|
89
|
+
# Returned if the pid_file does not exist.
|
90
|
+
#
|
91
|
+
def pid_from_file
|
92
|
+
File.read(pid_file).chomp.to_i
|
93
|
+
rescue Errno::ENOENT, Errno::EACCES
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Store the PID on the filesystem
|
98
|
+
# This uses the Pylon::Config[:pid_file] option, or "/tmp/name.pid" otherwise
|
99
|
+
#
|
100
|
+
def save_pid_file
|
101
|
+
file = pid_file
|
102
|
+
begin
|
103
|
+
FileUtils.mkdir_p(File.dirname(file))
|
104
|
+
rescue Errno::EACCES => e
|
105
|
+
Pylon::Application.fatal!("Failed store pid in #{File.dirname(file)}, permission denied: #{e.message}")
|
106
|
+
end
|
107
|
+
|
108
|
+
begin
|
109
|
+
File.open(file, "w") { |f| f.write(Process.pid.to_s) }
|
110
|
+
rescue Errno::EACCES => e
|
111
|
+
Pylon::Application.fatal!("Couldn't write to pidfile #{file}, permission denied: #{e.message}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Delete the PID from the filesystem
|
116
|
+
def remove_pid_file
|
117
|
+
FileUtils.rm(pid_file) if File.exists?(pid_file)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Change process user/group to those specified in Pylon::Config
|
121
|
+
#
|
122
|
+
def change_privilege
|
123
|
+
Dir.chdir("/")
|
124
|
+
|
125
|
+
if Pylon::Config[:user] and Pylon::Config[:group]
|
126
|
+
Pylon::Log.info("About to change privilege to #{Pylon::Config[:user]}:#{Pylon::Config[:group]}")
|
127
|
+
_change_privilege(Pylon::Config[:user], Pylon::Config[:group])
|
128
|
+
elsif Pylon::Config[:user]
|
129
|
+
Pylon::Log.info("About to change privilege to #{Pylon::Config[:user]}")
|
130
|
+
_change_privilege(Pylon::Config[:user])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Change privileges of the process to be the specified user and group
|
135
|
+
#
|
136
|
+
# ==== Parameters
|
137
|
+
# user<String>:: The user to change the process to.
|
138
|
+
# group<String>:: The group to change the process to.
|
139
|
+
#
|
140
|
+
# ==== Alternatives
|
141
|
+
# If group is left out, the user will be used (changing to user:user)
|
142
|
+
#
|
143
|
+
def _change_privilege(user, group=user)
|
144
|
+
uid, gid = Process.euid, Process.egid
|
145
|
+
|
146
|
+
begin
|
147
|
+
target_uid = Etc.getpwnam(user).uid
|
148
|
+
rescue ArgumentError => e
|
149
|
+
Pylon::Application.fatal!("Failed to get UID for user #{user}, does it exist? #{e.message}")
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
target_gid = Etc.getgrnam(group).gid
|
155
|
+
rescue ArgumentError => e
|
156
|
+
Pylon::Application.fatal!("Failed to get GID for group #{group}, does it exist? #{e.message}")
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
|
160
|
+
if (uid != target_uid) or (gid != target_gid)
|
161
|
+
Process.initgroups(user, target_gid)
|
162
|
+
Process::GID.change_privilege(target_gid)
|
163
|
+
Process::UID.change_privilege(target_uid)
|
164
|
+
end
|
165
|
+
true
|
166
|
+
rescue Errno::EPERM => e
|
167
|
+
Pylon::Application.fatal!("Permission denied when trying to change #{uid}:#{gid} to #{target_uid}:#{target_gid}. #{e.message}")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
#
|
2
|
+
# Author:: AJ Christensen (<aj@junglist.gen.nz>)
|
3
|
+
# Copyright:: Copyright (c) 2011 AJ Christensen
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or#implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require "uuidtools"
|
19
|
+
require "ffi-rzmq"
|
20
|
+
require "thread"
|
21
|
+
require "json"
|
22
|
+
require_relative "node"
|
23
|
+
|
24
|
+
class Pylon
|
25
|
+
class Elector
|
26
|
+
|
27
|
+
attr_accessor :cluster_name, :context, :multicast_endpoint, :node, :nodes, :multicast_announcer_thread, :multicast_listener_thread, :tcp_listener_thread, :master
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@cluster_name = Pylon::Config[:cluster_name]
|
31
|
+
@context = ZMQ::Context.new(1)
|
32
|
+
|
33
|
+
@node = Pylon::Node.new
|
34
|
+
@nodes = Array(@node)
|
35
|
+
@master = Pylon::Config[:master]
|
36
|
+
|
37
|
+
Pylon::Log.info "elector[#{cluster_name}] initialized, starting pub/sub sockets on #{multicast_endpoint} and tcp listener socket on #{node.unicast_endpoint}"
|
38
|
+
|
39
|
+
Thread.abort_on_exception = true
|
40
|
+
|
41
|
+
scheduler = Thread.new do
|
42
|
+
@unicast_announcer_thread = node.unicast_announcer
|
43
|
+
@multicast_announcer_thread = node.multicast_announcer
|
44
|
+
@multicast_listener_thread = multicast_listener
|
45
|
+
|
46
|
+
@multicast_listener_thread.join
|
47
|
+
@unicast_announcer_thread.join
|
48
|
+
@multicast_announcer_thread.join
|
49
|
+
end
|
50
|
+
scheduler.join
|
51
|
+
|
52
|
+
# join listeners
|
53
|
+
# @unicast_announcer_thread.join
|
54
|
+
# @tcp_listener_thread.join
|
55
|
+
# sleep 5
|
56
|
+
# @multicast_announcer_thread.join
|
57
|
+
|
58
|
+
at_exit do
|
59
|
+
Log.debug "cleaning up zeromq context"
|
60
|
+
@context.terminate
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop_announcer
|
65
|
+
@multicast_announcer_thread.stop
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_announcer
|
69
|
+
@multicast_announcer.thread.run
|
70
|
+
end
|
71
|
+
|
72
|
+
def pause_listeners
|
73
|
+
@tcp_listener_thread.stop
|
74
|
+
@multicast_listener_thread.stop
|
75
|
+
end
|
76
|
+
|
77
|
+
def run_listeners
|
78
|
+
@tcp_listener_thread.run
|
79
|
+
@multicst_listener_thread.run
|
80
|
+
end
|
81
|
+
|
82
|
+
def unicast_endpoint
|
83
|
+
@unicast_endpoint ||= "tcp://#{Pylon::Config[:tcp_address]}:#{Pylon::Config[:tcp_port]}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def multicast_endpoint
|
87
|
+
@multicast_endpoint ||= "epgm://#{Pylon::Config[:interface]};#{Pylon::Config[:multicast_address]}:#{Pylon::Config[:multicast_port]}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def add_node node
|
91
|
+
@nodes << node unless @nodes.include? node
|
92
|
+
end
|
93
|
+
|
94
|
+
def multicast_listener
|
95
|
+
Thread.new do
|
96
|
+
Log.debug "multicast_listener: zeromq sub socket starting up on #{@multicast_endpoint}"
|
97
|
+
sub_socket = context.socket ZMQ::SUB
|
98
|
+
sub_socket.setsockopt ZMQ::IDENTITY, "node"
|
99
|
+
sub_socket.setsockopt ZMQ::SUBSCRIBE, ""
|
100
|
+
sub_socket.setsockopt ZMQ::RATE, 1000
|
101
|
+
sub_socket.setsockopt ZMQ::MCAST_LOOP, Pylon::Config[:multicast_loopback]
|
102
|
+
sub_socket.connect multicast_endpoint
|
103
|
+
loop do
|
104
|
+
uuid = sub_socket.recv_string
|
105
|
+
Log.debug "multicast_listener: handling announce from #{uuid}"
|
106
|
+
handle_announce sub_socket.recv_string if sub_socket.more_parts?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def allocate_master
|
112
|
+
if node.uuid == nodes.sort.first.uuid
|
113
|
+
@master = true
|
114
|
+
Log.info "allocate_master: master allocated"
|
115
|
+
nodes.each do |node|
|
116
|
+
connect_node node.uuid.to_s, node.unicast_endpoint
|
117
|
+
end
|
118
|
+
else
|
119
|
+
Log.info "allocate_master: someone else is the master, getting ready for work"
|
120
|
+
@master = false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def handle_announce recv_string
|
125
|
+
Log.info "handle_announce: got string #{recv_string}"
|
126
|
+
node = JSON.parse(recv_string)
|
127
|
+
Log.info "handle_anounce: got announce from #{node}"
|
128
|
+
if master
|
129
|
+
Log.info "handle_announce: I am the master: updating #{node} of leadership status"
|
130
|
+
connect_node(node.uuid.to_s, node.unicast_endpoint)
|
131
|
+
elsif nodes.length < Pylon::Config[:minimum_master_nodes]
|
132
|
+
if nodes.include? node
|
133
|
+
Log.info "handle_announce: skipping node #{node}, already known"
|
134
|
+
Log.debug "handle_announce: nodes: #{nodes}"
|
135
|
+
else
|
136
|
+
Log.info "handle_announce: subscribing for #{node} on endpoint: #{node.unicast_endpoint}"
|
137
|
+
connect_node(node.uuid.to_s, node.unicast_endpoint)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
allocate_master
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def connect_node uuid, endpoint
|
145
|
+
Log.debug "connect_node: subscribe socket connecting to #{endpoint}"
|
146
|
+
sub_socket = context.socket ZMQ::SUB
|
147
|
+
sub_socket.setsockopt ZMQ::IDENTITY, uuid
|
148
|
+
sub_socket.setsockopt ZMQ::SUBSCRIBE, uuid
|
149
|
+
sub_socket.connect endpoint
|
150
|
+
uuid = sub_socket.recv_string
|
151
|
+
Log.debug "connect_node: got uuid on sub socket, parsing node"
|
152
|
+
new_node = JSON.parse(sub_socket.recv_string) if sub_socket.more_parts?
|
153
|
+
Log.debug "connect_node: node: #{new_node}"
|
154
|
+
if nodes.include? new_node
|
155
|
+
Log.info "connect_node: skipping node #{new_node}, already in local list, sleeping for 60 secs"
|
156
|
+
Log.debug "connect_node: nodes: #{nodes}"
|
157
|
+
sleep 60
|
158
|
+
else
|
159
|
+
Log.info "connect_node: connected to node #{new_node}, adding to local list"
|
160
|
+
add_node new_node
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|