etcd-tools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/bin/etcd-erb +3 -0
- data/bin/etcd-watchdog-haproxy +4 -0
- data/bin/etcd-watchdog-vip +4 -0
- data/bin/yaml2etcd +3 -0
- data/lib/etcd-tools/etcd_erb/erb.rb +26 -0
- data/lib/etcd-tools/etcd_erb/options.rb +29 -0
- data/lib/etcd-tools/etcd_erb.rb +38 -0
- data/lib/etcd-tools/etcd_watchdog_haproxy.rb +20 -0
- data/lib/etcd-tools/etcd_watchdog_vip.rb +82 -0
- data/lib/etcd-tools/mixins.rb +19 -0
- data/lib/etcd-tools/watchdog/config.rb +38 -0
- data/lib/etcd-tools/watchdog/etcd.rb +30 -0
- data/lib/etcd-tools/watchdog/haproxy.rb +7 -0
- data/lib/etcd-tools/watchdog/helpers.rb +23 -0
- data/lib/etcd-tools/watchdog/init.rb +48 -0
- data/lib/etcd-tools/watchdog/logger.rb +42 -0
- data/lib/etcd-tools/watchdog/threads/etcd.rb +20 -0
- data/lib/etcd-tools/watchdog/threads/icmp.rb +20 -0
- data/lib/etcd-tools/watchdog/vip.rb +98 -0
- data/lib/etcd-tools/yaml2etcd/import.rb +23 -0
- data/lib/etcd-tools/yaml2etcd/options.rb +37 -0
- data/lib/etcd-tools/yaml2etcd.rb +44 -0
- data/lib/etcd_tools.rb +3 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTdmMmY3NWMyMmE3NWEyYThiM2RkZDQ1YTkwMTY2MmNhOTM2NzEyMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZGQyZWVjNzY1YTU1YTJjMDY4MGIyMGE2MTU5MmJiNzJkYzU0MDc0Mw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjI4ZGQ2ZDFkZmZiMTIxMjg1ZGUzOTI0YWY3NDg4YTFlYTZhYWZhZThhYTM3
|
10
|
+
MTk3NGFmYjM1MjViZGY1MmNkOGQwY2Y2ZGYzZjQxOWUwNzhjYTYyODU3ZWM3
|
11
|
+
MTI4ZWIxMDA0ZjQ2NDc2ZDFmNDFiYTRhNGM3MjcyNjI1YmY5ODU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDZhZDc3ZjU1OWZiODM3ODQ2ODMxMWU5YTllMWM2ZWJmYjVkYjBmZGYzNTI5
|
14
|
+
NmVmY2NiNjY0ZTA5N2I3MzE1NWFmYzY4MTIzMTc3Njc1MGJlYmQwMWE5Y2Fl
|
15
|
+
YjY2YTRkOGJhNGZkNjVjOTk0MjdjYmU4NTM1MWY3OTg4ZmVjMTE=
|
data/bin/etcd-erb
ADDED
data/bin/yaml2etcd
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module EtcdERB
|
3
|
+
module Erb
|
4
|
+
def result
|
5
|
+
super binding
|
6
|
+
end
|
7
|
+
|
8
|
+
def value path
|
9
|
+
return @etcd.get('/' + path.sub(/^\//, '')).value
|
10
|
+
end
|
11
|
+
|
12
|
+
def keys path
|
13
|
+
path.sub!(/^\//, '')
|
14
|
+
if @etcd.get('/' + path).directory?
|
15
|
+
return @etcd.get('/' + path).children.map { |key| key.key }
|
16
|
+
else
|
17
|
+
return []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def template
|
22
|
+
ARGF.read
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module EtcdTools
|
4
|
+
module EtcdERB
|
5
|
+
module Options
|
6
|
+
def optparse
|
7
|
+
@options = Hash.new
|
8
|
+
|
9
|
+
@options[:url] = ENV['ETCDCTL_ENDPOINT']
|
10
|
+
@options[:url] ||= "http://127.0.0.1:4001"
|
11
|
+
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = "Applies variables from ETCD onto ERB template\n\nUsage: #{$0} [OPTIONS] < template.erb > outfile"
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Connection options:"
|
16
|
+
opts.on("-u", "--url URL", "URL endpoint of the ETCD service (ETCDCTL_ENDPOINT envvar also applies) [DEFAULT: http://127.0.0.1:4001]") do |param|
|
17
|
+
@options[:url] = param
|
18
|
+
end
|
19
|
+
opts.separator ""
|
20
|
+
opts.separator "Common options:"
|
21
|
+
opts.on_tail("-h", "--help", "show usage") do |param|
|
22
|
+
puts opts
|
23
|
+
exit! 0
|
24
|
+
end
|
25
|
+
end.parse!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require 'etcd'
|
4
|
+
require 'etcd-tools/etcd_erb/options'
|
5
|
+
require 'etcd-tools/etcd_erb/erb'
|
6
|
+
|
7
|
+
module EtcdTools
|
8
|
+
class EtcdERB < ERB
|
9
|
+
|
10
|
+
include EtcdTools::EtcdERB::Options
|
11
|
+
include EtcdTools::EtcdERB::Erb
|
12
|
+
|
13
|
+
attr_reader :etcd
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
self.optparse
|
17
|
+
@etcd = self.class.connect(@options[:url])
|
18
|
+
super self.class.template
|
19
|
+
puts self.result
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def connect (url)
|
24
|
+
(host, port) = url.gsub(/^https?:\/\//, '').gsub(/\/$/, '').split(':')
|
25
|
+
etcd = Etcd.client(host: host, port: port)
|
26
|
+
begin
|
27
|
+
etcd.version
|
28
|
+
return etcd
|
29
|
+
rescue Exception => e
|
30
|
+
$stderr.puts "Couldn't connect to etcd at #{host}:#{port}"
|
31
|
+
$stderr.puts e.message
|
32
|
+
exit! 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'net/ping'
|
4
|
+
|
5
|
+
module EtcdTools
|
6
|
+
module Watchdog
|
7
|
+
class HAproxy < EtcdTools::Watchdog::Init
|
8
|
+
|
9
|
+
include EtcdTools::Watchdog::HAproxy
|
10
|
+
|
11
|
+
def run
|
12
|
+
@thread = {
|
13
|
+
etcd: thread_etcd
|
14
|
+
}
|
15
|
+
@status_etcd = false
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'net/ping'
|
4
|
+
require 'etcd-tools/watchdog/init'
|
5
|
+
require 'etcd-tools/watchdog/vip'
|
6
|
+
require 'etcd-tools/watchdog/threads/icmp'
|
7
|
+
|
8
|
+
module EtcdTools
|
9
|
+
class VipWatchdog < EtcdTools::Watchdog::Init
|
10
|
+
|
11
|
+
include EtcdTools::Watchdog::Vip
|
12
|
+
|
13
|
+
def run
|
14
|
+
if Process.euid != 0
|
15
|
+
err 'Must run under root user!'
|
16
|
+
exit! 1
|
17
|
+
end
|
18
|
+
@semaphore.merge!({ icmp: Mutex.new })
|
19
|
+
@thread = { icmp: thread_icmp, etcd: thread_etcd }
|
20
|
+
@status_etcd = false
|
21
|
+
@status_icmp = false
|
22
|
+
@thread.each_value(&:run)
|
23
|
+
# sleep 5
|
24
|
+
first_cycle = true
|
25
|
+
while !@exit do
|
26
|
+
status_etcd = status_icmp = false # FIXME: introduce CVs...
|
27
|
+
@semaphore[:icmp].synchronize { status_icmp = @status_icmp }
|
28
|
+
@semaphore[:etcd].synchronize { status_etcd = @status_etcd }
|
29
|
+
if status_etcd
|
30
|
+
if got_vip?
|
31
|
+
debug '<main> i am the leader with VIP, that is OK'
|
32
|
+
else
|
33
|
+
info '<main> i am the leader without VIP, checking whether it is free'
|
34
|
+
if status_icmp
|
35
|
+
info '<main> VIP is still up! (ICMP)'
|
36
|
+
# FIXME: notify by sensu client socket
|
37
|
+
else
|
38
|
+
info '<main> VIP is unreachable by ICMP, checking for duplicates on L2'
|
39
|
+
if vip_dup?
|
40
|
+
info '<main> VIP is still assigned! (ARP)'
|
41
|
+
# FIXME: notify by sensu client socket
|
42
|
+
else
|
43
|
+
info '<main> VIP is free, assigning'
|
44
|
+
vip_handle! status_etcd
|
45
|
+
info '<main> updating other hosts about change'
|
46
|
+
vip_update_arp!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if got_vip?
|
52
|
+
info '<main> i got VIP and should not, removing'
|
53
|
+
vip_handle! status_etcd
|
54
|
+
info '<main> updating other hosts about change'
|
55
|
+
vip_update_arp!
|
56
|
+
else
|
57
|
+
debug '<main> i am not a leader and i do not have the VIP, that is OK'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
sleep @config[:parameters][:interval]
|
61
|
+
if first_cycle
|
62
|
+
@semaphore[:icmp].synchronize { status_icmp = @status_icmp }
|
63
|
+
@semaphore[:etcd].synchronize { status_etcd = @status_etcd }
|
64
|
+
info "<main> i #{status_etcd ? 'AM' : 'am NOT'} the leader"
|
65
|
+
info "<main> i #{got_vip? ? 'DO' : 'do NOT'} have the VIP"
|
66
|
+
info "<main> i #{status_icmp ? 'CAN' : 'CANNOT'} see the VIP"
|
67
|
+
end
|
68
|
+
first_cycle = false
|
69
|
+
end
|
70
|
+
info '<main> terminated!'
|
71
|
+
if got_vip?
|
72
|
+
info '<main> removing VIP'
|
73
|
+
vip_handle! false
|
74
|
+
vip_update_arp!
|
75
|
+
end
|
76
|
+
info '<main> stopping threads...'
|
77
|
+
@thread.each_value(&:join)
|
78
|
+
info '<main> exiting...'
|
79
|
+
exit 0
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Hash
|
2
|
+
def deep_merge(second)
|
3
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
4
|
+
self.merge(second, &merger)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Etcd
|
9
|
+
class Client
|
10
|
+
def members
|
11
|
+
members = JSON.parse(api_execute(version_prefix + '/members', :get, timeout: 10).body)['members']
|
12
|
+
Hash[members.map{|member| [ member['id'], member.tap { |h| h.delete('id') }]}]
|
13
|
+
end
|
14
|
+
|
15
|
+
def healthy?
|
16
|
+
JSON.parse(api_execute('/health', :get, timeout: 3).body)['health'] == 'true'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Watchdog
|
3
|
+
module Config
|
4
|
+
private
|
5
|
+
def default_config
|
6
|
+
{ debug: false,
|
7
|
+
parameters: { interface: 'eth0',
|
8
|
+
vip: '192.168.0.168',
|
9
|
+
mask: '255.255.255.0',
|
10
|
+
interval: 1,
|
11
|
+
etcd_endpoint: 'http://127.0.0.1:4001',
|
12
|
+
etcd_interval: 1,
|
13
|
+
etcd_timeout: 5,
|
14
|
+
icmp_count: 2,
|
15
|
+
icmp_interval: 1,
|
16
|
+
arping_count: 1,
|
17
|
+
arping_wait: 1 },
|
18
|
+
commands: { arping: `which arping`.chomp,
|
19
|
+
iproute: `which ip`.chomp,
|
20
|
+
arp: `which arp`.chomp } }
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
cfg = default_config
|
25
|
+
if File.exist? '/etc/etcd-watchdog.yaml'
|
26
|
+
cfg = cfg.deep_merge YAML.load_file('/etc/etcd-watchdog.yaml')
|
27
|
+
info '<main> loaded config from /etc/etcd-watchdog.yaml'
|
28
|
+
elsif File.exist? './etcd-watchdog.yaml'
|
29
|
+
cfg = cfg.deep_merge YAML.load_file('./etcd-watchdog.yaml')
|
30
|
+
info '<main> loaded config from ./etcd-watchdog.yaml'
|
31
|
+
else
|
32
|
+
info '<main> no config file loaded, using defaults'
|
33
|
+
end
|
34
|
+
cfg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Watchdog
|
3
|
+
module Etcd
|
4
|
+
# connect to ETCD
|
5
|
+
def etcd_connect!
|
6
|
+
(host, port) = @config[:parameters][:etcd_endpoint].gsub(/^https?:\/\//, '').gsub(/\/$/, '').split(':')
|
7
|
+
etcd = ::Etcd.client(host: host, port: port)
|
8
|
+
begin
|
9
|
+
versions = JSON.parse(etcd.version)
|
10
|
+
info "<etcd> conncted to ETCD at #{@config[:parameters][:etcd_endpoint]}"
|
11
|
+
info "<etcd> server version: #{versions['etcdserver']}"
|
12
|
+
info "<etcd> cluster version: #{versions['etcdcluster']}"
|
13
|
+
info "<etcd> healthy: #{etcd.healthy?}"
|
14
|
+
return etcd
|
15
|
+
rescue Exception => e
|
16
|
+
err "<etcd> couldn't connect to etcd at #{host}:#{port}"
|
17
|
+
err "<etcd> #{e.message}"
|
18
|
+
@exit = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# is my ETCD the leader?
|
23
|
+
# <IMPLEMENTED>
|
24
|
+
def leader?(etcd)
|
25
|
+
etcd.stats(:self)['id'] == etcd.stats(:self)['leaderInfo']['leader']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module EtcdTools
|
4
|
+
module Watchdog
|
5
|
+
module Helpers
|
6
|
+
def hostname
|
7
|
+
@hostname ||= Socket.gethostname
|
8
|
+
end
|
9
|
+
|
10
|
+
def arping
|
11
|
+
@config[:commands][:arping]
|
12
|
+
end
|
13
|
+
|
14
|
+
def iproute
|
15
|
+
@config[:commands][:iproute]
|
16
|
+
end
|
17
|
+
|
18
|
+
def arp
|
19
|
+
@config[:commands][:arp]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'timeout'
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require 'time'
|
6
|
+
require 'etcd'
|
7
|
+
require 'etcd-tools/mixins'
|
8
|
+
require 'etcd-tools/watchdog/config'
|
9
|
+
require 'etcd-tools/watchdog/logger'
|
10
|
+
require 'etcd-tools/watchdog/helpers'
|
11
|
+
require 'etcd-tools/watchdog/etcd'
|
12
|
+
require 'etcd-tools/watchdog/threads/etcd'
|
13
|
+
|
14
|
+
module EtcdTools
|
15
|
+
module Watchdog
|
16
|
+
class Init
|
17
|
+
|
18
|
+
include EtcdTools::Watchdog::Config
|
19
|
+
include EtcdTools::Watchdog::Logger
|
20
|
+
include EtcdTools::Watchdog::Helpers
|
21
|
+
include EtcdTools::Watchdog::Etcd
|
22
|
+
include EtcdTools::Watchdog::Threads
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@semaphore = {
|
26
|
+
log: Mutex.new,
|
27
|
+
etcd: Mutex.new
|
28
|
+
}
|
29
|
+
@config = { debug: false }
|
30
|
+
@config = config
|
31
|
+
@exit = false
|
32
|
+
# handle various signals
|
33
|
+
@exit_sigs = ['INT', 'TERM']
|
34
|
+
@exit_sigs.each { |sig| Signal.trap(sig) { @exit = true } }
|
35
|
+
Signal.trap('USR1') { @config[:debug] = false }
|
36
|
+
Signal.trap('USR2') { @config[:debug] = true }
|
37
|
+
Signal.trap('HUP') { @config = config }
|
38
|
+
if RUBY_VERSION >= '2.1'
|
39
|
+
Process.setproctitle('etcd-vip-watchdog')
|
40
|
+
else
|
41
|
+
$0 = 'etcd-vip-watchdog'
|
42
|
+
end
|
43
|
+
# Process.setpriority(Process::PRIO_PROCESS, 0, -20)
|
44
|
+
# Process.daemon
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module EtcdTools
|
4
|
+
module Watchdog
|
5
|
+
module Logger
|
6
|
+
def info(message)
|
7
|
+
if @config[:debug]
|
8
|
+
@semaphore[:log].synchronize do
|
9
|
+
$stdout.puts(Time.now.to_s + ' INFO (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
|
10
|
+
$stdout.flush
|
11
|
+
end
|
12
|
+
else
|
13
|
+
@semaphore[:log].synchronize do
|
14
|
+
$stdout.puts(Time.now.to_s + ' INFO ' + message.to_s)
|
15
|
+
$stdout.flush
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def err(message)
|
21
|
+
if @config[:debug]
|
22
|
+
@semaphore[:log].synchronize do
|
23
|
+
$stdout.puts(Time.now.to_s + ' ERROR (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
|
24
|
+
$stdout.flush
|
25
|
+
end
|
26
|
+
else
|
27
|
+
@semaphore[:log].synchronize do
|
28
|
+
$stdout.puts(Time.now.to_s + ' ERROR ' + message.to_s)
|
29
|
+
$stdout.flush
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def debug(message)
|
35
|
+
@semaphore[:log].synchronize do
|
36
|
+
$stdout.puts(Time.now.to_s + ' DEBUG (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
|
37
|
+
$stdout.flush
|
38
|
+
end if @config[:debug]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Watchdog
|
3
|
+
module Threads
|
4
|
+
def thread_etcd
|
5
|
+
Thread.new do
|
6
|
+
debug '<etcd> starting thread...'
|
7
|
+
etcd = etcd_connect!
|
8
|
+
while !@exit do
|
9
|
+
debug '<etcd> checking etcd state'
|
10
|
+
status = leader? etcd
|
11
|
+
@semaphore[:etcd].synchronize { @status_etcd = status }
|
12
|
+
debug "<etcd> i am #{status ? 'the leader' : 'not a leader' }"
|
13
|
+
sleep @config[:parameters][:etcd_interval]
|
14
|
+
end
|
15
|
+
info '<etcd> ending thread...'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Watchdog
|
3
|
+
module Threads
|
4
|
+
def thread_icmp
|
5
|
+
Thread.new do
|
6
|
+
debug '<icmp> starting thread...'
|
7
|
+
icmp = Net::Ping::ICMP.new(@config[:parameters][:vip])
|
8
|
+
while !@exit do
|
9
|
+
debug '<icmp> checking state by ping'
|
10
|
+
status = vip_alive? icmp
|
11
|
+
@semaphore[:icmp].synchronize { @status_icmp = status }
|
12
|
+
debug "<icmp> VIP is #{status ? 'alive' : 'down' }"
|
13
|
+
sleep @config[:parameters][:icmp_interval]
|
14
|
+
end
|
15
|
+
info '<icmp> ending thread...'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Watchdog
|
3
|
+
module Vip
|
4
|
+
# add or remove VIP on interface
|
5
|
+
# <IMPLEMENTED>
|
6
|
+
def vip_handle!(leader)
|
7
|
+
ip = IPAddr.new(@config[:parameters][:vip])
|
8
|
+
mask = @config[:parameters][:mask]
|
9
|
+
cmd = [ iproute,
|
10
|
+
'address',
|
11
|
+
'',
|
12
|
+
"#{ip}/#{mask}",
|
13
|
+
'dev',
|
14
|
+
@config[:parameters][:interface],
|
15
|
+
'label',
|
16
|
+
@config[:parameters][:interface] + '-vip',
|
17
|
+
'>/dev/null 2>&1'
|
18
|
+
]
|
19
|
+
case leader
|
20
|
+
when true
|
21
|
+
cmd[2] = 'add'
|
22
|
+
when false
|
23
|
+
cmd[2] = 'delete'
|
24
|
+
end
|
25
|
+
debug "CMD #{cmd.join(' ')}"
|
26
|
+
if system(cmd.join(' '))
|
27
|
+
info "IP '#{cmd[2]}' operation done"
|
28
|
+
else
|
29
|
+
err "IP '#{cmd[2]}' operation failed"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# send gratuitous ARP to the network
|
34
|
+
# <IMPLEMENTED>
|
35
|
+
def vip_update_arp!
|
36
|
+
cmd = [ arping, '-U',
|
37
|
+
'-c', @config[:parameters][:arping_count],
|
38
|
+
'-I', @config[:parameters][:interface],
|
39
|
+
@config[:parameters][:vip], '>/dev/null 2>&1' ]
|
40
|
+
debug "CMD #{cmd.join(' ')}"
|
41
|
+
if system(cmd.join(' '))
|
42
|
+
info 'gratuitous ARP packet sent'
|
43
|
+
return true
|
44
|
+
else
|
45
|
+
err 'gratuitous ARP packet failed to send'
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# check whether VIP is assigned to me
|
51
|
+
# <IMPLEMENTED>
|
52
|
+
def got_vip?
|
53
|
+
cmd = [ iproute,
|
54
|
+
'address',
|
55
|
+
'show',
|
56
|
+
'label',
|
57
|
+
"#{@config[:parameters][:interface]}-vip",
|
58
|
+
'|',
|
59
|
+
'grep',
|
60
|
+
'-q',
|
61
|
+
"#{@config[:parameters][:interface]}-vip"
|
62
|
+
]
|
63
|
+
debug "CMD #{cmd.join(' ')}"
|
64
|
+
if system(cmd.join(' '))
|
65
|
+
return true
|
66
|
+
else
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# check reachability of VIP by ICMP echo
|
72
|
+
# <--- REWORK
|
73
|
+
def vip_alive?(icmp)
|
74
|
+
(1..@config[:parameters][:icmp_count]).each { return true if icmp.ping }
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
# check whether the IP is registered anywhere
|
79
|
+
#
|
80
|
+
def vip_dup?
|
81
|
+
cmd_arp = [ arp, '-d', @config[:parameters][:vip], '>/dev/null 2>&1' ]
|
82
|
+
cmd_arping = [ arping, '-D',
|
83
|
+
'-c', @config[:parameters][:arping_count],
|
84
|
+
'-w', @config[:parameters][:arping_wait],
|
85
|
+
'-I', @config[:parameters][:interface],
|
86
|
+
@config[:parameters][:vip], '>/dev/null 2>&1' ]
|
87
|
+
debug "CMD #{cmd_arp.join(' ')}"
|
88
|
+
system(cmd_arp.join(' '))
|
89
|
+
debug "CMD #{cmd_arping.join(' ')}"
|
90
|
+
if system(cmd_arping.join(' '))
|
91
|
+
return false
|
92
|
+
else
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EtcdTools
|
2
|
+
module Yaml2Etcd
|
3
|
+
module Methods
|
4
|
+
def import_structure (hash, path="")
|
5
|
+
begin
|
6
|
+
hash.each do |k, v|
|
7
|
+
etcd_key = path + "/" + k.to_s
|
8
|
+
case v
|
9
|
+
when Hash
|
10
|
+
import_structure(v, etcd_key)
|
11
|
+
else
|
12
|
+
@etcd.set(etcd_key, value: v)
|
13
|
+
puts("SET: " + etcd_key + ": " + v.to_json) if @options[:verbose]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
rescue Exception => e
|
17
|
+
$stderr.puts "Configuration import failed"
|
18
|
+
$stderr.puts e.message
|
19
|
+
exit! 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module EtcdTools
|
4
|
+
module Yaml2Etcd
|
5
|
+
module Options
|
6
|
+
def optparse
|
7
|
+
@options = Hash.new
|
8
|
+
|
9
|
+
@options[:url] = ENV['ETCDCTL_ENDPOINT']
|
10
|
+
@options[:url] ||= "http://127.0.0.1:4001"
|
11
|
+
@options[:root_path] = "/config"
|
12
|
+
|
13
|
+
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = "Reads YAML file and imports the data into ETCD\n\nUsage: #{$0} [OPTIONS] < config.yaml"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Connection options:"
|
18
|
+
opts.on("-u", "--url HOST", "URL endpoint of the ETCD service (ETCDCTL_ENDPOINT envvar also applies) [DEFAULT: http://127.0.0.1:4001]") do |param|
|
19
|
+
@options[:url] = param
|
20
|
+
end
|
21
|
+
opts.separator ""
|
22
|
+
opts.separator "Common options:"
|
23
|
+
opts.on("-r", "--root-path PATH", "root PATH of ETCD tree to inject the data [DEFAULT: /config]") do |param|
|
24
|
+
@options[:root_path] = param
|
25
|
+
end
|
26
|
+
opts.on("-v", "--verbose", "run verbosely") do |param|
|
27
|
+
@options[:verbose] = param
|
28
|
+
end
|
29
|
+
opts.on_tail("-h", "--help", "show usage") do |param|
|
30
|
+
puts opts;
|
31
|
+
exit! 0
|
32
|
+
end
|
33
|
+
end.parse!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'etcd'
|
3
|
+
require_relative 'etcd_tools/yaml2etcd/options'
|
4
|
+
require_relative 'etcd_tools/yaml2etcd/import'
|
5
|
+
|
6
|
+
module EtcdTools
|
7
|
+
class Yaml2Etcd
|
8
|
+
|
9
|
+
include EtcdTools::Yaml2Etcd::Options
|
10
|
+
include EtcdTools::Yaml2Etcd::Import
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
self.optparse
|
14
|
+
@etcd = self.class.connect(@options[:url], @options[:verbose])
|
15
|
+
@hash = self.class.read_yaml
|
16
|
+
import_structure @hash, @options[:root_path]
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def read_yaml
|
21
|
+
begin
|
22
|
+
return YAML.load(ARGF.read)
|
23
|
+
rescue
|
24
|
+
$stderr.puts "Couldn't parse YAML"
|
25
|
+
exit! 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect (url, verbose=false)
|
30
|
+
(host, port) = url.gsub(/^https?:\/\//, '').gsub(/\/$/, '').split(':')
|
31
|
+
etcd = Etcd.client(host: host, port: port)
|
32
|
+
begin
|
33
|
+
etcd.version
|
34
|
+
puts "Connected to ETCD on #{host}:#{port}" if verbose
|
35
|
+
return etcd
|
36
|
+
rescue Exception => e
|
37
|
+
$stderr.puts "Couldn't connect to etcd at #{host}:#{port}"
|
38
|
+
$stderr.puts e.message
|
39
|
+
exit! 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/lib/etcd_tools.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: etcd-tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Radek 'blufor' Slavicinsky
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: etcd
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.3.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.3'
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.3.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: net-ping
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.7'
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.7.8
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ~>
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.7'
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.7.8
|
53
|
+
description: A set of handful CLIE ETCD tools, part of PortAuthority
|
54
|
+
email: radek.slavicinsky@gmail.com
|
55
|
+
executables:
|
56
|
+
- etcd-watchdog-haproxy
|
57
|
+
- etcd-watchdog-vip
|
58
|
+
- etcd-erb
|
59
|
+
- yaml2etcd
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- bin/etcd-erb
|
64
|
+
- bin/etcd-watchdog-haproxy
|
65
|
+
- bin/etcd-watchdog-vip
|
66
|
+
- bin/yaml2etcd
|
67
|
+
- lib/etcd-tools/etcd_erb.rb
|
68
|
+
- lib/etcd-tools/etcd_erb/erb.rb
|
69
|
+
- lib/etcd-tools/etcd_erb/options.rb
|
70
|
+
- lib/etcd-tools/etcd_watchdog_haproxy.rb
|
71
|
+
- lib/etcd-tools/etcd_watchdog_vip.rb
|
72
|
+
- lib/etcd-tools/mixins.rb
|
73
|
+
- lib/etcd-tools/watchdog/config.rb
|
74
|
+
- lib/etcd-tools/watchdog/etcd.rb
|
75
|
+
- lib/etcd-tools/watchdog/haproxy.rb
|
76
|
+
- lib/etcd-tools/watchdog/helpers.rb
|
77
|
+
- lib/etcd-tools/watchdog/init.rb
|
78
|
+
- lib/etcd-tools/watchdog/logger.rb
|
79
|
+
- lib/etcd-tools/watchdog/threads/etcd.rb
|
80
|
+
- lib/etcd-tools/watchdog/threads/icmp.rb
|
81
|
+
- lib/etcd-tools/watchdog/vip.rb
|
82
|
+
- lib/etcd-tools/yaml2etcd.rb
|
83
|
+
- lib/etcd-tools/yaml2etcd/import.rb
|
84
|
+
- lib/etcd-tools/yaml2etcd/options.rb
|
85
|
+
- lib/etcd_tools.rb
|
86
|
+
homepage: http://rubygems.org/gems/etcd-tools
|
87
|
+
licenses:
|
88
|
+
- GPLv2
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '1.9'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.3
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: CLI ETCD tools
|
110
|
+
test_files: []
|