etcd-tools 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.
- 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: []
|