port-authority 0.4.2 → 0.4.3
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.
- checksums.yaml +13 -5
- data/bin/pa-manager +3 -0
- data/lib/port-authority/manager/app.rb +153 -0
- data/lib/port-authority/manager/init.rb +43 -0
- data/lib/port-authority/manager/threads/icmp.rb +30 -0
- data/lib/port-authority/manager/threads/swarm.rb +42 -0
- data/lib/port-authority/util/config.rb +61 -0
- data/lib/port-authority/util/etcd.rb +33 -0
- data/lib/port-authority/util/helpers.rb +27 -0
- data/lib/port-authority/util/loadbalancer.rb +66 -0
- data/lib/port-authority/util/logger.rb +57 -0
- data/lib/port-authority/util/vip.rb +57 -0
- data/lib/port-authority.rb +28 -0
- metadata +35 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
YjA2NzA1NWY3MjM5ZjE2NWFkZmE4NjdkOWFjMWU4NmU4M2E5M2QyMQ==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
Y2FlNTQ0OWI0ZmY4YmE3NzhhMDI1NjZlZTlhNjZiZTNjYjdiYzliNQ==
|
|
5
7
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
NmRjZGE4MDZiZjhiM2ZmZGM3ODVjMjZmZTIzOWE2ZGJmMjQ3ZGY3MzE5YmQ1
|
|
10
|
+
OTVlNjJmMzkwYjhlYjg4MDFiYzFmNmZhYzFhNDdkOWUxYmQ4MTZjMjU2NzUx
|
|
11
|
+
NzA5MGI3MTlmYzA5MDdlOGJlMGQxMzBlMzEzNTY0YWI3ZTAyYzc=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
MDQ4YTc0NDIzMDBlZjRiNzVjYTNjMTIwMWZlYjg2YzBiODkyNmQ0ZGJjOTli
|
|
14
|
+
MmZjNDk0NGE1OTZkNjY0MzBkYzc0NjdlMmVjMmRkZDA1OTQ2NTUwNDc3Y2I4
|
|
15
|
+
NmFjNzQ1NDYwNzEwMDdhMDMxOTIwYjc4YTI1OGI3ZGIwMWRmOWI=
|
data/bin/pa-manager
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# rubocop:disable MethodLength, CyclomaticComplexity, Metrics/BlockNesting, Metrics/LineLength, Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
2
|
+
require 'ipaddr'
|
|
3
|
+
require 'port-authority'
|
|
4
|
+
require 'port-authority/util/vip'
|
|
5
|
+
require 'port-authority/util/etcd'
|
|
6
|
+
require 'port-authority/util/loadbalancer'
|
|
7
|
+
require 'port-authority/manager/init'
|
|
8
|
+
require 'port-authority/manager/threads/icmp'
|
|
9
|
+
require 'port-authority/manager/threads/swarm'
|
|
10
|
+
|
|
11
|
+
module PortAuthority
|
|
12
|
+
module Manager
|
|
13
|
+
##
|
|
14
|
+
# Port Authority Manager - manages floating VIP and lb placement
|
|
15
|
+
#
|
|
16
|
+
class App < PortAuthority::Manager::Init
|
|
17
|
+
include PortAuthority::Util::Etcd
|
|
18
|
+
include PortAuthority::Util::Vip
|
|
19
|
+
include PortAuthority::Util::LoadBalancer
|
|
20
|
+
include PortAuthority::Manager::Threads
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
# exit if not root
|
|
24
|
+
if Process.euid != 0
|
|
25
|
+
alert 'must run under root user!'
|
|
26
|
+
exit! 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Signal.trap('USR1') { @lb_update_hook = true }
|
|
30
|
+
|
|
31
|
+
# prepare semaphores
|
|
32
|
+
@semaphore.merge!(swarm: Mutex.new, icmp: Mutex.new)
|
|
33
|
+
|
|
34
|
+
# prepare threads
|
|
35
|
+
@thread = {icmp: thread_icmp,swarm: thread_swarm}
|
|
36
|
+
|
|
37
|
+
# prepare status vars
|
|
38
|
+
@status_swarm = false
|
|
39
|
+
@status_icmp = false
|
|
40
|
+
|
|
41
|
+
# start threads
|
|
42
|
+
@thread.each_value(&:run)
|
|
43
|
+
|
|
44
|
+
# setup docker client
|
|
45
|
+
lb_docker_setup! || @exit = true
|
|
46
|
+
|
|
47
|
+
# prepare container with load-balancer
|
|
48
|
+
lb_create!
|
|
49
|
+
|
|
50
|
+
# wait for threads to make sure they gather something
|
|
51
|
+
debug 'waiting for threads to gather something...'
|
|
52
|
+
sleep @config[:vip][:interval]
|
|
53
|
+
first_cycle = true
|
|
54
|
+
status_time = Time.now.to_i - 60
|
|
55
|
+
|
|
56
|
+
# main loop
|
|
57
|
+
until @exit
|
|
58
|
+
# initialize local state vars on first iteration
|
|
59
|
+
status_swarm = status_icmp = false if first_cycle
|
|
60
|
+
|
|
61
|
+
if @lb_update_hook
|
|
62
|
+
notice 'updating LB image'
|
|
63
|
+
lb_update!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# iteration interval
|
|
67
|
+
sleep @config[:vip][:interval]
|
|
68
|
+
|
|
69
|
+
# sync state to local variables
|
|
70
|
+
@semaphore[:icmp].synchronize { status_icmp = @status_icmp }
|
|
71
|
+
@semaphore[:swarm].synchronize { status_swarm = @status_swarm }
|
|
72
|
+
|
|
73
|
+
# the logic (should be self-explanatory ;))
|
|
74
|
+
if status_swarm
|
|
75
|
+
debug 'i am the leader'
|
|
76
|
+
if got_vip?
|
|
77
|
+
debug 'got VIP, that is OK'
|
|
78
|
+
else
|
|
79
|
+
info 'no VIP here, checking whether it is free'
|
|
80
|
+
if status_icmp
|
|
81
|
+
info 'VIP is still up! (ICMP)'
|
|
82
|
+
# FIXME: notify by sensu client socket
|
|
83
|
+
else
|
|
84
|
+
# FIXME: proper arping handling
|
|
85
|
+
# info 'VIP is unreachable by ICMP, checking for duplicates on L2'
|
|
86
|
+
# if vip_dup?
|
|
87
|
+
# info 'VIP is still assigned! (ARP)'
|
|
88
|
+
# # FIXME: notify by sensu client socket
|
|
89
|
+
# else
|
|
90
|
+
# info 'VIP is free :) assigning'
|
|
91
|
+
# vip_handle! status_swarm
|
|
92
|
+
# info 'updating other hosts about change'
|
|
93
|
+
# vip_update_arp!
|
|
94
|
+
# end
|
|
95
|
+
notice 'VIP is free :) assigning'
|
|
96
|
+
vip_handle! status_swarm
|
|
97
|
+
notice 'updating other hosts about change'
|
|
98
|
+
vip_update_arp!
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
if lb_up?
|
|
102
|
+
debug 'load-balancer is up, that is OK'
|
|
103
|
+
else
|
|
104
|
+
notice 'load-balancer is down, starting'
|
|
105
|
+
lb_start!
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
debug 'i am not the leader'
|
|
109
|
+
if got_vip?
|
|
110
|
+
notice 'i got VIP and should not, removing'
|
|
111
|
+
vip_handle! status_swarm
|
|
112
|
+
notice 'updating other hosts about change'
|
|
113
|
+
vip_update_arp!
|
|
114
|
+
else
|
|
115
|
+
debug 'no VIP here, that is OK'
|
|
116
|
+
end
|
|
117
|
+
if lb_up?
|
|
118
|
+
notice 'load-balancer is up, stopping'
|
|
119
|
+
lb_stop!
|
|
120
|
+
else
|
|
121
|
+
debug 'load-balancer is down, that is OK'
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if status_time + 60 <= Time.now.to_i
|
|
126
|
+
info "STATUS_REPORT { leader: '#{status_swarm ? 'yes' : 'no'}', vip: '#{got_vip? ? 'yes' : 'no'}/#{status_icmp ? 'up' : 'down'}', lb: '#{lb_up? ? 'yes' : 'no'}' }"
|
|
127
|
+
status_time = Time.now.to_i
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# this is triggerred on exit
|
|
133
|
+
@thread.each_value(&:join)
|
|
134
|
+
|
|
135
|
+
# remove VIP on shutdown
|
|
136
|
+
if got_vip?
|
|
137
|
+
notice 'removing VIP'
|
|
138
|
+
vip_handle! false
|
|
139
|
+
vip_update_arp!
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# stop LB on shutdown
|
|
143
|
+
if lb_up?
|
|
144
|
+
notice 'stopping load-balancer'
|
|
145
|
+
lb_stop!
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
info 'exiting...'
|
|
149
|
+
exit 0
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'timeout'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'etcd-tools'
|
|
4
|
+
require 'port-authority/util/config'
|
|
5
|
+
require 'port-authority/util/logger'
|
|
6
|
+
require 'port-authority/util/helpers'
|
|
7
|
+
|
|
8
|
+
module PortAuthority
|
|
9
|
+
module Manager
|
|
10
|
+
class Init
|
|
11
|
+
include PortAuthority::Util::Config
|
|
12
|
+
include PortAuthority::Util::Logger
|
|
13
|
+
include PortAuthority::Util::Helpers
|
|
14
|
+
|
|
15
|
+
def initialize(proc_name = 'dummy')
|
|
16
|
+
@config = config
|
|
17
|
+
@exit = false
|
|
18
|
+
@semaphore = { log: Mutex.new }
|
|
19
|
+
Thread.current[:name] = 'main'
|
|
20
|
+
syslog_init proc_name if @config[:syslog]
|
|
21
|
+
setup proc_name
|
|
22
|
+
info 'starting main thread'
|
|
23
|
+
debug 'setting signal handling'
|
|
24
|
+
@exit_sigs = %w(INT TERM)
|
|
25
|
+
@exit_sigs.each { |sig| Signal.trap(sig) { @exit = true } }
|
|
26
|
+
Signal.trap('USR2') { @config[:debug] = !@config[:debug] }
|
|
27
|
+
Signal.trap('HUP') { @config = config }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def setup(proc_name, nice = -20)
|
|
31
|
+
debug 'setting process name'
|
|
32
|
+
if RUBY_VERSION >= '2.1'
|
|
33
|
+
Process.setproctitle(proc_name)
|
|
34
|
+
else
|
|
35
|
+
$0 = proc_name
|
|
36
|
+
end
|
|
37
|
+
debug 'setting process title'
|
|
38
|
+
Process.setpriority(Process::PRIO_PROCESS, 0, nice)
|
|
39
|
+
# FIXME: Process.daemon ...
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# rubocop:disable Metrics/MethodLength
|
|
2
|
+
require 'net/ping'
|
|
3
|
+
|
|
4
|
+
module PortAuthority
|
|
5
|
+
module Manager
|
|
6
|
+
module Threads
|
|
7
|
+
def thread_icmp
|
|
8
|
+
Thread.new do
|
|
9
|
+
Thread.current[:name] = 'icmp'
|
|
10
|
+
begin
|
|
11
|
+
info 'starting ICMP thread...'
|
|
12
|
+
icmp = Net::Ping::ICMP.new(@config[:vip][:ip])
|
|
13
|
+
until @exit
|
|
14
|
+
debug 'checking state by ICMP echo'
|
|
15
|
+
status = vip_alive? icmp
|
|
16
|
+
@semaphore[:icmp].synchronize { @status_icmp = status }
|
|
17
|
+
debug "VIP is #{status ? 'alive' : 'down'} according to ICMP"
|
|
18
|
+
sleep @config[:icmp][:interval]
|
|
19
|
+
end
|
|
20
|
+
info 'ending ICMP thread...'
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
alert "#{e.class}: #{e.message}"
|
|
23
|
+
alert e.backtrace
|
|
24
|
+
@exit = true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# rubocop:disable Metrics/MethodLength
|
|
2
|
+
module PortAuthority
|
|
3
|
+
module Manager
|
|
4
|
+
module Threads
|
|
5
|
+
def thread_swarm
|
|
6
|
+
Thread.new do
|
|
7
|
+
Thread.current[:name] = 'swarm'
|
|
8
|
+
info 'starting swarm thread...'
|
|
9
|
+
begin
|
|
10
|
+
etcd = etcd_connect!
|
|
11
|
+
until @exit
|
|
12
|
+
debug 'checking ETCD state'
|
|
13
|
+
etcd_healthy? etcd
|
|
14
|
+
debug 'checking swarm state'
|
|
15
|
+
status = am_i_leader? etcd
|
|
16
|
+
@semaphore[:swarm].synchronize { @status_swarm = status }
|
|
17
|
+
debug "i am #{status ? 'the leader' : 'not the leader' }"
|
|
18
|
+
sleep @config[:etcd][:interval]
|
|
19
|
+
end
|
|
20
|
+
info 'ending swarm thread...'
|
|
21
|
+
rescue PortAuthority::Errors::ETCDIsSick => e
|
|
22
|
+
notice "#{e.class}: #{e.message}"
|
|
23
|
+
notice "connection: " + e.etcd.to_s
|
|
24
|
+
@semaphore[:swarm].synchronize { @status_swarm = false }
|
|
25
|
+
sleep @config[:etcd][:interval]
|
|
26
|
+
retry unless @exit
|
|
27
|
+
rescue PortAuthority::Errors::ETCDConnectFailed => e
|
|
28
|
+
err "#{e.class}: #{e.message}"
|
|
29
|
+
err "connection: " + e.etcd.to_s
|
|
30
|
+
@semaphore[:swarm].synchronize { @status_swarm = false }
|
|
31
|
+
sleep @config[:etcd][:interval]
|
|
32
|
+
retry unless @exit
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
alert e.message
|
|
35
|
+
alert e.backtrace.to_s
|
|
36
|
+
@exit = true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'etcd-tools'
|
|
3
|
+
|
|
4
|
+
module PortAuthority
|
|
5
|
+
module Util
|
|
6
|
+
module Config
|
|
7
|
+
|
|
8
|
+
def config
|
|
9
|
+
cfg = default_config
|
|
10
|
+
if File.exist? '/etc/port-authority.yaml'
|
|
11
|
+
cfg = cfg.deep_merge YAML.load_file('/etc/port-authority.yaml')
|
|
12
|
+
puts 'loaded config from /etc/port-authority.yaml'
|
|
13
|
+
elsif File.exist? './port-authority.yaml'
|
|
14
|
+
cfg = cfg.deep_merge YAML.load_file('./port-authority.yaml')
|
|
15
|
+
puts 'loaded config from ./port-authority.yaml'
|
|
16
|
+
else
|
|
17
|
+
puts 'no config file loaded, using defaults'
|
|
18
|
+
end
|
|
19
|
+
cfg
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def default_config
|
|
25
|
+
{ debug: false,
|
|
26
|
+
syslog: false,
|
|
27
|
+
etcd: {
|
|
28
|
+
endpoints: ['http://localhost:2379'],
|
|
29
|
+
interval: 5,
|
|
30
|
+
timeout: 5
|
|
31
|
+
},
|
|
32
|
+
icmp: {
|
|
33
|
+
count: 5,
|
|
34
|
+
interval: 2
|
|
35
|
+
},
|
|
36
|
+
arping: {
|
|
37
|
+
count: 1,
|
|
38
|
+
wait: 1
|
|
39
|
+
},
|
|
40
|
+
vip: {
|
|
41
|
+
interval: 1,
|
|
42
|
+
ip: '172.17.1.5',
|
|
43
|
+
mask: '255.255.255.0',
|
|
44
|
+
interface: 'eth0'
|
|
45
|
+
},
|
|
46
|
+
lb: {
|
|
47
|
+
image: 'docker-registry.prz/stackdocks/haproxy:latest',
|
|
48
|
+
name: 'lb',
|
|
49
|
+
network: 'overlay',
|
|
50
|
+
docker_endpoint: 'unix:///var/run/docker.sock'
|
|
51
|
+
},
|
|
52
|
+
commands: {
|
|
53
|
+
arping: `which arping`.chomp,
|
|
54
|
+
arp: `which arp`.chomp,
|
|
55
|
+
iproute: `which ip`.chomp
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'etcd'
|
|
2
|
+
require 'etcd-tools/mixins'
|
|
3
|
+
|
|
4
|
+
module PortAuthority
|
|
5
|
+
module Util
|
|
6
|
+
module Etcd
|
|
7
|
+
# connect to ETCD
|
|
8
|
+
def etcd_connect!
|
|
9
|
+
endpoints = @config[:etcd][:endpoints].map { |e| e = e.gsub!(/^https?:\/\//, '').gsub(/\/$/, '').split(':'); { host: e[0], port: e[1].to_i } }
|
|
10
|
+
debug "parsed ETCD endpoints: #{endpoints.to_s}"
|
|
11
|
+
etcd = ::Etcd::Client.new(cluster: endpoints, read_timeout: @config[:etcd][:timeout])
|
|
12
|
+
etcd if etcd.version
|
|
13
|
+
rescue
|
|
14
|
+
raise PortAuthority::Errors::ETCDConnectFailed.new(@config[:etcd][:endpoints])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def etcd_healthy?(etcd)
|
|
18
|
+
raise PortAuthority::Errors::ETCDIsSick.new(@config[:etcd][:endpoints]) unless etcd.healthy?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def swarm_leader(etcd)
|
|
22
|
+
etcd.get('/_pa/docker/swarm/leader').value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def am_i_leader?(etcd)
|
|
26
|
+
Socket.ip_address_list.map(&:ip_address).member?(swarm_leader(etcd).split(':').first)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module PortAuthority
|
|
4
|
+
module Util
|
|
5
|
+
module Helpers
|
|
6
|
+
def hostname
|
|
7
|
+
@hostname ||= Socket.gethostname
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def my_ip
|
|
11
|
+
@my_ip ||= Socket.ip_address_list.detect(&:ipv4_private?).ip_address
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def arping
|
|
15
|
+
@config[:commands][:arping]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def iproute
|
|
19
|
+
@config[:commands][:iproute]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def arp
|
|
23
|
+
@config[:commands][:arp]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'docker-api'
|
|
2
|
+
|
|
3
|
+
module PortAuthority
|
|
4
|
+
module Util
|
|
5
|
+
module LoadBalancer
|
|
6
|
+
# connect to Docker
|
|
7
|
+
def lb_docker_setup!
|
|
8
|
+
Docker.url = @config[:lb][:docker_endpoint]
|
|
9
|
+
Docker.version
|
|
10
|
+
true
|
|
11
|
+
rescue
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def lb_update!
|
|
16
|
+
lb_stop! if lb_up?
|
|
17
|
+
lb_remove!
|
|
18
|
+
lb_create!
|
|
19
|
+
@lb_update_hook = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def lb_remove!
|
|
23
|
+
Docker::Container.get(@config[:lb][:name]).delete
|
|
24
|
+
rescue Docker::Error::NotFoundError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def lb_create!
|
|
29
|
+
lb_remove!
|
|
30
|
+
img = Docker::Image.create('fromImage' => @config[:lb][:image])
|
|
31
|
+
|
|
32
|
+
# setup port bindings hash
|
|
33
|
+
port_bindings = Hash.new
|
|
34
|
+
img.json['ContainerConfig']['ExposedPorts'].keys.each do |port|
|
|
35
|
+
port_bindings[port] = [ { 'HostPort' => "#{port.split('/').first}" } ]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# create container with
|
|
39
|
+
@lb_container = Docker::Container.create(
|
|
40
|
+
'Image' => img.json['Id'],
|
|
41
|
+
'name' => @config[:lb][:name],
|
|
42
|
+
'Hostname' => @config[:lb][:name],
|
|
43
|
+
'Env' => [ "ETCDCTL_ENDPOINT=#{@config[:etcd][:endpoints].join(',')}" ],
|
|
44
|
+
'RestartPolicy' => { 'Name' => 'never' },
|
|
45
|
+
'HostConfig' => {
|
|
46
|
+
'PortBindings' => port_bindings,
|
|
47
|
+
'NetworkMode' => @config[:lb][:network]
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def lb_up?
|
|
53
|
+
@lb_container.json['State']['Running']
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def lb_start!
|
|
57
|
+
@lb_container.start
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def lb_stop!
|
|
61
|
+
@lb_container.stop
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# rubocop:disable Metrics/LineLength, Metrics/AbcSize, Metrics/MethodLength
|
|
2
|
+
require 'syslog'
|
|
3
|
+
|
|
4
|
+
module PortAuthority
|
|
5
|
+
module Util
|
|
6
|
+
module Logger
|
|
7
|
+
def debug(message)
|
|
8
|
+
log :debug, message if @config[:debug]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def info(message)
|
|
12
|
+
log :info, message
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def notice(message)
|
|
16
|
+
log :notice, message
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def err(message)
|
|
20
|
+
log :err, message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def alert(message)
|
|
24
|
+
log :alert, message if @config[:debug]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def syslog_init(proc_name)
|
|
28
|
+
Syslog.open(proc_name, Syslog::LOG_PID, Syslog::LOG_DAEMON)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def log(lvl, msg)
|
|
32
|
+
if @config[:syslog]
|
|
33
|
+
case lvl
|
|
34
|
+
when :debug
|
|
35
|
+
l = Syslog::LOG_DEBUG
|
|
36
|
+
when :info
|
|
37
|
+
l = Syslog::LOG_INFO
|
|
38
|
+
when :notice
|
|
39
|
+
l = Syslog::LOG_NOTICE
|
|
40
|
+
when :err
|
|
41
|
+
l = Syslog::LOG_ERR
|
|
42
|
+
when :alert
|
|
43
|
+
l = Syslog::LOG_ALERT
|
|
44
|
+
end
|
|
45
|
+
@semaphore[:log].synchronize do
|
|
46
|
+
Syslog.log(l, "(%s) %s", Thread.current[:name], msg.to_s)
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
@semaphore[:log].synchronize do
|
|
50
|
+
$stdout.puts("#{Time.now.to_s} #{lvl.to_s[0].capitalize} (#{Thread.current[:name]}) #{msg.to_s}")
|
|
51
|
+
$stdout.flush
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# rubocop:disable Metrics/LineLength, Metrics/AbcSize
|
|
2
|
+
module PortAuthority
|
|
3
|
+
module Util
|
|
4
|
+
module Vip
|
|
5
|
+
# add or remove VIP on interface
|
|
6
|
+
# <IMPLEMENTED>
|
|
7
|
+
def vip_handle!(leader)
|
|
8
|
+
ip = IPAddr.new(@config[:vip][:ip])
|
|
9
|
+
mask = @config[:vip][:mask]
|
|
10
|
+
cmd = [iproute, 'address', '', "#{ip}/#{mask}", 'dev', @config[:vip][:interface], 'label', @config[:vip][:interface] + '-vip', '>/dev/null 2>&1']
|
|
11
|
+
leader ? cmd[2] = 'add' : cmd[2] = 'delete'
|
|
12
|
+
debug "#{cmd.join(' ')}"
|
|
13
|
+
if system(cmd.join(' '))
|
|
14
|
+
return true
|
|
15
|
+
else
|
|
16
|
+
return false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# send gratuitous ARP to the network
|
|
21
|
+
def vip_update_arp!
|
|
22
|
+
cmd = [arping, '-U', '-q', '-c', @config[:arping][:count], '-I', @config[:vip][:interface], @config[:vip][:ip]]
|
|
23
|
+
debug "#{cmd.join(' ')}"
|
|
24
|
+
if system(cmd.join(' '))
|
|
25
|
+
return true
|
|
26
|
+
else
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# check whether VIP is assigned to me
|
|
32
|
+
def got_vip?
|
|
33
|
+
Socket.ip_address_list.map(&:ip_address).member?(@config[:vip][:ip])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# check reachability of VIP by ICMP echo
|
|
37
|
+
def vip_alive?(icmp)
|
|
38
|
+
(1..@config[:icmp][:count]).each { return true if icmp.ping }
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# check whether the IP is registered anywhere
|
|
43
|
+
def vip_dup?
|
|
44
|
+
cmd_arp = [arp, '-d', @config[:vip][:ip], '>/dev/null 2>&1']
|
|
45
|
+
cmd_arping = [arping, '-D', '-q', '-c', @config[:arping][:count], '-w', @config[:arping][:wait], '-I', @config[:vip][:interface], @config[:vip][:ip]]
|
|
46
|
+
debug "#{cmd_arp.join(' ')}"
|
|
47
|
+
system(cmd_arp.join(' '))
|
|
48
|
+
debug "#{cmd_arping.join(' ')}"
|
|
49
|
+
if system(cmd_arping.join(' '))
|
|
50
|
+
return false
|
|
51
|
+
else
|
|
52
|
+
return true
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module PortAuthority
|
|
2
|
+
module Manager
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module Util
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Errors
|
|
9
|
+
|
|
10
|
+
class ETCDConnectFailed < StandardError
|
|
11
|
+
attr_reader :etcd, :message
|
|
12
|
+
def initialize(etcd, message = "Can't connect to ETCD")
|
|
13
|
+
@message = message
|
|
14
|
+
@etcd = etcd
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ETCDIsSick < StandardError
|
|
19
|
+
attr_reader :etcd, :message
|
|
20
|
+
def initialize(etcd, message = 'ETCD is not healthy')
|
|
21
|
+
@message = message
|
|
22
|
+
@etcd = etcd
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: port-authority
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Radek 'blufor' Slavicinsky
|
|
@@ -14,88 +14,101 @@ dependencies:
|
|
|
14
14
|
name: etcd
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- -
|
|
17
|
+
- - ~>
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '0.3'
|
|
20
|
-
- -
|
|
20
|
+
- - ! '>='
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
22
|
version: 0.3.0
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
26
|
requirements:
|
|
27
|
-
- -
|
|
27
|
+
- - ~>
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
29
|
version: '0.3'
|
|
30
|
-
- -
|
|
30
|
+
- - ! '>='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: 0.3.0
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
34
|
name: etcd-tools
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- -
|
|
37
|
+
- - ~>
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.4'
|
|
40
|
-
- -
|
|
40
|
+
- - ! '>='
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
42
|
version: 0.4.0
|
|
43
43
|
type: :runtime
|
|
44
44
|
prerelease: false
|
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
46
46
|
requirements:
|
|
47
|
-
- -
|
|
47
|
+
- - ~>
|
|
48
48
|
- !ruby/object:Gem::Version
|
|
49
49
|
version: '0.4'
|
|
50
|
-
- -
|
|
50
|
+
- - ! '>='
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: 0.4.0
|
|
53
53
|
- !ruby/object:Gem::Dependency
|
|
54
54
|
name: net-ping
|
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
|
56
56
|
requirements:
|
|
57
|
-
- -
|
|
57
|
+
- - ~>
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '1.7'
|
|
60
|
-
- -
|
|
60
|
+
- - ! '>='
|
|
61
61
|
- !ruby/object:Gem::Version
|
|
62
62
|
version: 1.7.8
|
|
63
63
|
type: :runtime
|
|
64
64
|
prerelease: false
|
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
66
66
|
requirements:
|
|
67
|
-
- -
|
|
67
|
+
- - ~>
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
69
|
version: '1.7'
|
|
70
|
-
- -
|
|
70
|
+
- - ! '>='
|
|
71
71
|
- !ruby/object:Gem::Version
|
|
72
72
|
version: 1.7.8
|
|
73
73
|
- !ruby/object:Gem::Dependency
|
|
74
74
|
name: docker-api
|
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
|
76
76
|
requirements:
|
|
77
|
-
- -
|
|
77
|
+
- - ~>
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '1.0'
|
|
80
|
-
- -
|
|
80
|
+
- - ! '>='
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: 1.25.0
|
|
83
83
|
type: :runtime
|
|
84
84
|
prerelease: false
|
|
85
85
|
version_requirements: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- -
|
|
87
|
+
- - ~>
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
89
|
version: '1.0'
|
|
90
|
-
- -
|
|
90
|
+
- - ! '>='
|
|
91
91
|
- !ruby/object:Gem::Version
|
|
92
92
|
version: 1.25.0
|
|
93
93
|
description: CLI Tools for PortAuthority
|
|
94
94
|
email: radek.slavicinsky@gmail.com
|
|
95
|
-
executables:
|
|
95
|
+
executables:
|
|
96
|
+
- pa-manager
|
|
96
97
|
extensions: []
|
|
97
98
|
extra_rdoc_files: []
|
|
98
|
-
files:
|
|
99
|
+
files:
|
|
100
|
+
- bin/pa-manager
|
|
101
|
+
- lib/port-authority.rb
|
|
102
|
+
- lib/port-authority/manager/app.rb
|
|
103
|
+
- lib/port-authority/manager/init.rb
|
|
104
|
+
- lib/port-authority/manager/threads/icmp.rb
|
|
105
|
+
- lib/port-authority/manager/threads/swarm.rb
|
|
106
|
+
- lib/port-authority/util/config.rb
|
|
107
|
+
- lib/port-authority/util/etcd.rb
|
|
108
|
+
- lib/port-authority/util/helpers.rb
|
|
109
|
+
- lib/port-authority/util/loadbalancer.rb
|
|
110
|
+
- lib/port-authority/util/logger.rb
|
|
111
|
+
- lib/port-authority/util/vip.rb
|
|
99
112
|
homepage: https://github.com/prozeta/port-authority
|
|
100
113
|
licenses:
|
|
101
114
|
- GPLv2
|
|
@@ -106,17 +119,17 @@ require_paths:
|
|
|
106
119
|
- lib
|
|
107
120
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
121
|
requirements:
|
|
109
|
-
- -
|
|
122
|
+
- - ! '>='
|
|
110
123
|
- !ruby/object:Gem::Version
|
|
111
124
|
version: 1.9.3
|
|
112
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
126
|
requirements:
|
|
114
|
-
- -
|
|
127
|
+
- - ! '>='
|
|
115
128
|
- !ruby/object:Gem::Version
|
|
116
129
|
version: '0'
|
|
117
130
|
requirements: []
|
|
118
131
|
rubyforge_project:
|
|
119
|
-
rubygems_version: 2.4.
|
|
132
|
+
rubygems_version: 2.4.3
|
|
120
133
|
signing_key:
|
|
121
134
|
specification_version: 4
|
|
122
135
|
summary: Port Authority
|