etcd-tools 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTA1MTQ3MGQ3ZTdkMDMxYmFlN2VhOGE3ZjNmYmQwNjI2MmI4MTBjNA==
4
+ NjJmMWFjMTQ0ZTg4YTU1ZWYzODEyNzFkMTcxMzlkNzljYjM0ZTMyMw==
5
5
  data.tar.gz: !binary |-
6
- NGZiZTcxNzMxZmVjZjE4OTRiYzk0Mjc5YjBmNTg2YWI1NTk0MGU4YQ==
6
+ NTY4ZDgzNWM2MWNhNDhmZTc4MzYzOGZjNWQ4YTNiZWMwMzU0NmE2OA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZmQxZjc5NmRjYTlhMjEzMmMyMWNkYzk2OWM2NjEzZDI1OTkwYjJjMjUzYzli
10
- MWVkYmM4ZWQwMjRkNzg5OWI0NzMzNGQ0ZTRhNGNiZDE3M2FhZjkyMjlhN2Q1
11
- OTFmMjFmYTFhMmQ3ODM1MWI1NmQ2OTBkMmE5ODVmZjA3NWY2NjA=
9
+ M2NkMGYxZjRlYTNkMDM3MGIxZjk4ZmQ5ZTBmMzA4NjhmZGZkNGJjMjFiMGM0
10
+ YjFjNTc3MDdjMTk5MmQ5ZTUyNjcyMjQ3NTQyMTRlNmI0ZDcwN2Y3NWZjZmJm
11
+ NmY5ODkzMTA5NTdjOTkzNTQ4OTE1MTJiZDNkOGI0ZmY1ZmZmM2U=
12
12
  data.tar.gz: !binary |-
13
- ZjA0Mjc4MzNkMmExZDYxN2RjNmZhNjU2N2EwMDJkMDA3NjcwYjkxZWVkYWQ3
14
- OTczOTg5OWE3ODYyYWZkMzY0ODIxYmI4MzRlM2ZmYjI2ZmU4NGUxMGJlNGIy
15
- NjYzMTQ5ZGJmNmFjZTQ0MDgxMDViNGI1YWNhNzhmZGVkOWQ2NjA=
13
+ ZjU0OWE3ODM1MTM1MDM1Njk3MmYzYWZlNTZmMDk1NGU0MTQ5NzdhY2EzZWI1
14
+ OTEyNDlmZmExYzJlZWQzNThjMGE4ZjAxM2JjOTJjOWQyOGRmZDJkYWNkNGEy
15
+ Mjk0NTY4MDllMzRjNjk2YTc1Yzk0ZGU1NDExOWJiMjdlNzE3ZTA=
data/bin/etcd-erb CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'etcd-tools/etcd_erb'
3
- EtcdTools::EtcdERB.new
2
+ require 'etcd-tools/cli/etcd_erb'
3
+ EtcdTools::Cli::EtcdERB.new
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require 'etcd-tools/etcd_watchdog_vip'
3
- app = EtcdTools::VipWatchdog.new
2
+ require 'etcd-tools/watchdog/vip'
3
+ app = EtcdTools::Watchdog::Vip.new
4
4
  app.run
data/bin/etcd2yaml ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'etcd-tools/cli/etcd2yaml'
3
+ EtcdTools::Cli::Etcd2Yaml.new
data/bin/yaml2etcd CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'etcd-tools/yaml2etcd'
3
- EtcdTools::Yaml2Etcd.new
2
+ require 'etcd-tools/cli/yaml2etcd'
3
+ EtcdTools::Cli::Yaml2Etcd.new
@@ -0,0 +1,62 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+ require 'etcd-tools/etcd'
4
+
5
+ module EtcdTools
6
+ module Cli
7
+ class Etcd2Yaml
8
+ include EtcdTools::Etcd
9
+
10
+ def optparse
11
+ @options = Hash.new
12
+
13
+ @options[:url] = ENV['ETCDCTL_ENDPOINT']
14
+ @options[:url] ||= "http://127.0.0.1:4001"
15
+ @options[:root_path] = "/config"
16
+
17
+ OptionParser.new do |opts|
18
+ opts.banner = "Parses ETCD tree into structured YAML\n\nUsage: #{$0} [OPTIONS]"
19
+ opts.separator ""
20
+ opts.separator "Connection options:"
21
+ 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|
22
+ @options[:url] = param
23
+ end
24
+ opts.separator ""
25
+ opts.separator "Common options:"
26
+ opts.on("-r", "--root-path PATH", "root PATH of ETCD tree to extract the data from [DEFAULT: /config]") do |param|
27
+ @options[:root_path] = param
28
+ end
29
+ opts.on("-v", "--verbose", "run verbosely") do |param|
30
+ @options[:verbose] = param
31
+ end
32
+ opts.on_tail("-h", "--help", "show usage") do |param|
33
+ puts opts;
34
+ exit! 0
35
+ end
36
+ end.parse!
37
+ end
38
+
39
+ def initialize
40
+ self.optparse
41
+
42
+ begin
43
+ @etcd = etcd_connect @options[:url]
44
+ rescue Exception => e
45
+ $stderr.puts "Failed to connect to ETCD!"
46
+ $stderr.puts e.message
47
+ exit! 1
48
+ end
49
+
50
+ begin
51
+ hash = etcd2hash @etcd, @options[:root_path]
52
+ puts hash.to_yaml
53
+ rescue Exception => e
54
+ $stderr.puts "Import failed"
55
+ $stderr.puts e.message
56
+ exit! 1
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -1,8 +1,12 @@
1
1
  require 'optparse'
2
+ require 'etcd-tools/etcd'
3
+ require 'etcd-tools/erb'
2
4
 
3
5
  module EtcdTools
4
- module EtcdERB
5
- module Options
6
+ module Cli
7
+ class EtcdERB
8
+ include EtcdTools::Etcd
9
+
6
10
  def optparse
7
11
  @options = Hash.new
8
12
 
@@ -24,6 +28,27 @@ module EtcdTools
24
28
  end
25
29
  end.parse!
26
30
  end
31
+
32
+ def initialize
33
+ self.optparse
34
+
35
+ begin
36
+ @etcd = etcd_connect @options[:url]
37
+ rescue Exception => e
38
+ $stderr.puts "Failed to connect to ETCD!"
39
+ $stderr.puts e.message
40
+ exit! 1
41
+ end
42
+
43
+ begin
44
+ template = EtcdTools::Erb.new @etcd, ARGF.read
45
+ puts template.result
46
+ rescue Exception => e
47
+ $stderr.puts "Failed to parse ERB template!"
48
+ $stderr.puts e.message
49
+ exit! 1
50
+ end
51
+ end
27
52
  end
28
53
  end
29
54
  end
@@ -1,8 +1,11 @@
1
1
  require 'optparse'
2
+ require 'etcd-tools/etcd'
2
3
 
3
4
  module EtcdTools
4
- module Yaml2Etcd
5
- module Options
5
+ module Cli
6
+ class Yaml2Etcd
7
+ include EtcdTools::Etcd
8
+
6
9
  def optparse
7
10
  @options = Hash.new
8
11
 
@@ -10,7 +13,6 @@ module EtcdTools
10
13
  @options[:url] ||= "http://127.0.0.1:4001"
11
14
  @options[:root_path] = "/config"
12
15
 
13
-
14
16
  OptionParser.new do |opts|
15
17
  opts.banner = "Reads YAML file and imports the data into ETCD\n\nUsage: #{$0} [OPTIONS] < config.yaml"
16
18
  opts.separator ""
@@ -32,6 +34,36 @@ module EtcdTools
32
34
  end
33
35
  end.parse!
34
36
  end
37
+
38
+ def initialize
39
+ self.optparse
40
+
41
+ begin
42
+ @hash = YAML.load ARGF.read
43
+ rescue
44
+ $stderr.puts "Failed to parse YAML!"
45
+ $stderr.puts e.message
46
+ exit! 1
47
+ end
48
+
49
+ begin
50
+ @etcd = etcd_connect @options[:url]
51
+ rescue Exception => e
52
+ $stderr.puts "Failed to connect to ETCD!"
53
+ $stderr.puts e.message
54
+ exit! 1
55
+ end
56
+
57
+ begin
58
+ hash2etcd @hash, @options[:root_path]
59
+ puts "OK"
60
+ rescue Exception => e
61
+ $stderr.puts "Import failed"
62
+ $stderr.puts e.message
63
+ exit! 1
64
+ end
65
+ end
66
+
35
67
  end
36
68
  end
37
69
  end
@@ -0,0 +1,37 @@
1
+ require 'erb'
2
+ require 'etcd-tools/etcd'
3
+
4
+ module EtcdTools
5
+ class Erb < ::ERB
6
+ include EtcdTools::Etcd
7
+
8
+ attr_reader :etcd
9
+
10
+ def initialize (etcd, template)
11
+ @etcd = etcd
12
+ super template
13
+ end
14
+
15
+ def result
16
+ super binding
17
+ end
18
+
19
+ def value path
20
+ return @etcd.get('/' + path.sub(/^\//, '')).value
21
+ end
22
+
23
+ def keys path
24
+ path.sub!(/^\//, '')
25
+ if @etcd.get('/' + path).directory?
26
+ return @etcd.get('/' + path).children.map { |key| key.key }
27
+ else
28
+ return []
29
+ end
30
+ end
31
+
32
+ def members
33
+ @etcd.members
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ require 'etcd'
2
+ require 'etcd-tools/mixins'
3
+
4
+ module EtcdTools
5
+ module Etcd
6
+ def etcd_connect (url)
7
+ (host, port) = url.gsub(/^https?:\/\//, '').gsub(/\/$/, '').split(':')
8
+ etcd = ::Etcd.client(host: host, port: port)
9
+ begin
10
+ etcd.version
11
+ return etcd
12
+ rescue Exception => e
13
+ raise e #fixme
14
+ end
15
+ end
16
+
17
+ def hash2etcd (etcd, hash, path="")
18
+ begin
19
+ hash.each do |key, value|
20
+ etcd_key = path + "/" + key.to_s
21
+ case value
22
+ when Hash
23
+ hash2etcd(value, etcd_key)
24
+ else
25
+ etcd.set(etcd_key, value: value)
26
+ end
27
+ end
28
+ rescue Exception => e
29
+ raise e #fixme
30
+ end
31
+ end
32
+
33
+ def etcd2hash (etcd, path="")
34
+ begin
35
+ hash = Hash.new
36
+ etcd.get(path).children.each do |child|
37
+ if etcd.get(child.key).directory?
38
+ hash[child.key.split('/').last.to_sym] = etcd2hash etcd, child.key
39
+ else
40
+ hash[child.key.split('/').last.to_sym] = child.value
41
+ end
42
+ end
43
+ rescue
44
+ return nil
45
+ end
46
+ return hash.sort.to_h
47
+ end
48
+
49
+ end
50
+ end
@@ -1,7 +1,23 @@
1
+ require 'etcd-tools/watchdog/init'
2
+ # require 'etcd-tools/watchdogthreads/haproxy'
3
+ require 'etcd-tools/erb'
4
+
1
5
  module EtcdTools
2
6
  module Watchdog
3
- module HAproxy
4
- # TODO
7
+ class HAproxy < EtcdTools::Watchdog::Init
8
+
9
+ def run
10
+ while @etcd.watch(@config[:haproxy_cfg]) do
11
+
12
+ end
13
+ end
14
+
15
+ def generate_config
16
+ end
17
+
18
+ def reload_haproxy
19
+ end
20
+
5
21
  end
6
22
  end
7
23
  end
@@ -1,47 +1,42 @@
1
- require 'ipaddr'
2
1
  require 'timeout'
3
- require 'yaml'
4
2
  require 'json'
5
3
  require 'time'
6
- require 'etcd'
7
4
  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'
5
+ require 'etcd-tools/watchdog/util/config'
6
+ require 'etcd-tools/watchdog/util/logger'
7
+ require 'etcd-tools/watchdog/util/helpers'
8
+ require 'etcd-tools/watchdog/util/etcd'
12
9
  require 'etcd-tools/watchdog/threads/etcd'
13
10
 
14
11
  module EtcdTools
15
12
  module Watchdog
16
13
  class Init
17
14
 
18
- include EtcdTools::Watchdog::Config
19
- include EtcdTools::Watchdog::Logger
20
- include EtcdTools::Watchdog::Helpers
21
- include EtcdTools::Watchdog::Etcd
15
+ include EtcdTools::Watchdog::Util::Config
16
+ include EtcdTools::Watchdog::Util::Logger
17
+ include EtcdTools::Watchdog::Util::Helpers
18
+ include EtcdTools::Watchdog::Util::Etcd
22
19
  include EtcdTools::Watchdog::Threads
23
20
 
24
21
  def initialize
25
- @semaphore = {
26
- log: Mutex.new,
27
- etcd: Mutex.new
28
- }
29
22
  @config = { debug: false }
30
23
  @config = config
31
24
  @exit = false
32
- # handle various signals
33
25
  @exit_sigs = ['INT', 'TERM']
34
26
  @exit_sigs.each { |sig| Signal.trap(sig) { @exit = true } }
35
27
  Signal.trap('USR1') { @config[:debug] = false }
36
28
  Signal.trap('USR2') { @config[:debug] = true }
37
- Signal.trap('HUP') { @config = config }
29
+ Signal.trap('HUP') { @config = config }
30
+ end
31
+
32
+ def setup(proc_name, nice = -20)
38
33
  if RUBY_VERSION >= '2.1'
39
- Process.setproctitle('etcd-vip-watchdog')
34
+ Process.setproctitle(proc_name)
40
35
  else
41
- $0 = 'etcd-vip-watchdog'
36
+ $0 = proc_name
42
37
  end
43
- # Process.setpriority(Process::PRIO_PROCESS, 0, -20)
44
- # Process.daemon
38
+ Process.setpriority(Process::PRIO_PROCESS, 0, nice)
39
+ # TODO: Process.daemon ...
45
40
  end
46
41
  end
47
42
  end
@@ -1,3 +1,5 @@
1
+ require 'net/ping'
2
+
1
3
  module EtcdTools
2
4
  module Watchdog
3
5
  module Threads
@@ -0,0 +1,43 @@
1
+ require 'yaml'
2
+ require 'etcd-tools/mixins'
3
+
4
+ module EtcdTools
5
+ module Watchdog
6
+ module Util
7
+ module Config
8
+ private
9
+ def default_config
10
+ { debug: false,
11
+ parameters: { interface: 'eth0',
12
+ vip: '192.168.0.168',
13
+ mask: '255.255.255.0',
14
+ interval: 1,
15
+ etcd_endpoint: 'http://127.0.0.1:4001',
16
+ etcd_interval: 1,
17
+ etcd_timeout: 5,
18
+ icmp_count: 2,
19
+ icmp_interval: 1,
20
+ arping_count: 1,
21
+ arping_wait: 1 },
22
+ commands: { arping: `which arping`.chomp,
23
+ iproute: `which ip`.chomp,
24
+ arp: `which arp`.chomp } }
25
+ end
26
+
27
+ def config
28
+ cfg = default_config
29
+ if File.exist? '/etc/etcd-watchdog.yaml'
30
+ cfg = cfg.deep_merge YAML.load_file('/etc/etcd-watchdog.yaml')
31
+ puts 'loaded config from /etc/etcd-watchdog.yaml'
32
+ elsif File.exist? './etcd-watchdog.yaml'
33
+ cfg = cfg.deep_merge YAML.load_file('./etcd-watchdog.yaml')
34
+ puts 'loaded config from ./etcd-watchdog.yaml'
35
+ else
36
+ puts 'no config file loaded, using defaults'
37
+ end
38
+ cfg
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ require 'etcd'
2
+
3
+ module EtcdTools
4
+ module Watchdog
5
+ module Util
6
+ module Etcd
7
+ # connect to ETCD
8
+ def etcd_connect!
9
+ (host, port) = @config[:parameters][:etcd_endpoint].gsub(/^https?:\/\//, '').gsub(/\/$/, '').split(':')
10
+ etcd = ::Etcd.client(host: host, port: port)
11
+ begin
12
+ versions = JSON.parse(etcd.version)
13
+ info "<etcd> conncted to ETCD at #{@config[:parameters][:etcd_endpoint]}"
14
+ info "<etcd> server version: #{versions['etcdserver']}"
15
+ info "<etcd> cluster version: #{versions['etcdcluster']}"
16
+ info "<etcd> healthy: #{etcd.healthy?}"
17
+ return etcd
18
+ rescue Exception => e
19
+ err "<etcd> couldn't connect to etcd at #{host}:#{port}"
20
+ err "<etcd> #{e.message}"
21
+ @exit = true
22
+ end
23
+ end
24
+
25
+ # is my ETCD the leader?
26
+ # <IMPLEMENTED>
27
+ def leader?(etcd)
28
+ etcd.stats(:self)['id'] == etcd.stats(:self)['leaderInfo']['leader']
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'socket'
2
+
3
+ module EtcdTools
4
+ module Watchdog
5
+ module Util
6
+ module Helpers
7
+ def hostname
8
+ @hostname ||= Socket.gethostname
9
+ end
10
+
11
+ def arping
12
+ @config[:commands][:arping]
13
+ end
14
+
15
+ def iproute
16
+ @config[:commands][:iproute]
17
+ end
18
+
19
+ def arp
20
+ @config[:commands][:arp]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ require 'logger'
2
+
3
+ module EtcdTools
4
+ module Watchdog
5
+ module Util
6
+ module Logger
7
+ def info(message)
8
+ if @config[:debug]
9
+ @semaphore[:log].synchronize do
10
+ $stdout.puts(Time.now.to_s + ' INFO (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
11
+ $stdout.flush
12
+ end
13
+ else
14
+ @semaphore[:log].synchronize do
15
+ $stdout.puts(Time.now.to_s + ' INFO ' + message.to_s)
16
+ $stdout.flush
17
+ end
18
+ end
19
+ end
20
+
21
+ def err(message)
22
+ if @config[:debug]
23
+ @semaphore[:log].synchronize do
24
+ $stdout.puts(Time.now.to_s + ' ERROR (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
25
+ $stdout.flush
26
+ end
27
+ else
28
+ @semaphore[:log].synchronize do
29
+ $stdout.puts(Time.now.to_s + ' ERROR ' + message.to_s)
30
+ $stdout.flush
31
+ end
32
+ end
33
+ end
34
+
35
+ def debug(message)
36
+ @semaphore[:log].synchronize do
37
+ $stdout.puts(Time.now.to_s + ' DEBUG (TID:' + Thread.current.object_id.to_s + ') ' + message.to_s)
38
+ $stdout.flush
39
+ end if @config[:debug]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,6 +1,85 @@
1
+ require 'ipaddr'
2
+ require 'etcd-tools/watchdog/init'
3
+ require 'etcd-tools/watchdog/threads/icmp'
4
+
1
5
  module EtcdTools
2
6
  module Watchdog
3
- module Vip
7
+ class Vip < EtcdTools::Watchdog::Init
8
+
9
+ def run
10
+ if Process.euid != 0
11
+ $stderr.puts 'Must run under root user!'
12
+ exit! 1
13
+ end
14
+ setup
15
+ @semaphore = {
16
+ log: Mutex.new,
17
+ etcd: Mutex.new,
18
+ icmp: Mutex.new
19
+ }
20
+ @thread = { icmp: thread_icmp, etcd: thread_etcd }
21
+ @status_etcd = false
22
+ @status_icmp = false
23
+ @thread.each_value(&:run)
24
+ sleep @config[:parameters][:interval]
25
+ first_cycle = true
26
+ while !@exit do
27
+ status_etcd = status_icmp = false # FIXME: introduce CVs...
28
+ @semaphore[:icmp].synchronize { status_icmp = @status_icmp }
29
+ @semaphore[:etcd].synchronize { status_etcd = @status_etcd }
30
+ if status_etcd
31
+ if got_vip?
32
+ debug '<main> i am the leader with VIP, that is OK'
33
+ else
34
+ info '<main> i am the leader without VIP, checking whether it is free'
35
+ if status_icmp
36
+ info '<main> VIP is still up! (ICMP)'
37
+ # FIXME: notify by sensu client socket
38
+ else
39
+ info '<main> VIP is unreachable by ICMP, checking for duplicates on L2'
40
+ if vip_dup?
41
+ info '<main> VIP is still assigned! (ARP)'
42
+ # FIXME: notify by sensu client socket
43
+ else
44
+ info '<main> VIP is free, assigning'
45
+ vip_handle! status_etcd
46
+ info '<main> updating other hosts about change'
47
+ vip_update_arp!
48
+ end
49
+ end
50
+ end
51
+ else
52
+ if got_vip?
53
+ info '<main> i got VIP and should not, removing'
54
+ vip_handle! status_etcd
55
+ info '<main> updating other hosts about change'
56
+ vip_update_arp!
57
+ else
58
+ debug '<main> i am not a leader and i do not have the VIP, that is OK'
59
+ end
60
+ end
61
+ sleep @config[:parameters][:interval]
62
+ if first_cycle
63
+ @semaphore[:icmp].synchronize { status_icmp = @status_icmp }
64
+ @semaphore[:etcd].synchronize { status_etcd = @status_etcd }
65
+ info "<main> i #{status_etcd ? 'AM' : 'am NOT'} the leader"
66
+ info "<main> i #{got_vip? ? 'DO' : 'do NOT'} have the VIP"
67
+ info "<main> i #{status_icmp ? 'CAN' : 'CANNOT'} see the VIP"
68
+ end
69
+ first_cycle = false
70
+ end
71
+ info '<main> terminated!'
72
+ if got_vip?
73
+ info '<main> removing VIP'
74
+ vip_handle! false
75
+ vip_update_arp!
76
+ end
77
+ info '<main> stopping threads...'
78
+ @thread.each_value(&:join)
79
+ info '<main> exiting...'
80
+ exit 0
81
+ end
82
+
4
83
  # add or remove VIP on interface
5
84
  # <IMPLEMENTED>
6
85
  def vip_handle!(leader)
@@ -16,17 +95,12 @@ module EtcdTools
16
95
  @config[:parameters][:interface] + '-vip',
17
96
  '>/dev/null 2>&1'
18
97
  ]
19
- case leader
20
- when true
21
- cmd[2] = 'add'
22
- when false
23
- cmd[2] = 'delete'
24
- end
25
- debug "CMD #{cmd.join(' ')}"
98
+ leader ? cmd[2] = 'add' : cmd[2] = 'delete'
99
+ debug "<shell> #{cmd.join(' ')}"
26
100
  if system(cmd.join(' '))
27
- info "IP '#{cmd[2]}' operation done"
101
+ return true
28
102
  else
29
- err "IP '#{cmd[2]}' operation failed"
103
+ return false
30
104
  end
31
105
  end
32
106
 
@@ -37,12 +111,10 @@ module EtcdTools
37
111
  '-c', @config[:parameters][:arping_count],
38
112
  '-I', @config[:parameters][:interface],
39
113
  @config[:parameters][:vip] ]
40
- debug "CMD #{cmd.join(' ')}"
114
+ debug "<shell> #{cmd.join(' ')}"
41
115
  if system(cmd.join(' '))
42
- info 'gratuitous ARP packet sent'
43
116
  return true
44
117
  else
45
- err 'gratuitous ARP packet failed to send'
46
118
  return false
47
119
  end
48
120
  end
@@ -60,7 +132,7 @@ module EtcdTools
60
132
  '-q',
61
133
  "#{@config[:parameters][:interface]}-vip"
62
134
  ]
63
- debug "CMD #{cmd.join(' ')}"
135
+ debug "<shell> #{cmd.join(' ')}"
64
136
  if system(cmd.join(' '))
65
137
  return true
66
138
  else
@@ -84,9 +156,9 @@ module EtcdTools
84
156
  '-w', @config[:parameters][:arping_wait],
85
157
  '-I', @config[:parameters][:interface],
86
158
  @config[:parameters][:vip] ]
87
- debug "CMD #{cmd_arp.join(' ')}"
159
+ debug "<shell> #{cmd_arp.join(' ')}"
88
160
  system(cmd_arp.join(' '))
89
- debug "CMD #{cmd_arping.join(' ')}"
161
+ debug "<shell> #{cmd_arping.join(' ')}"
90
162
  if system(cmd_arping.join(' '))
91
163
  return false
92
164
  else
data/lib/etcd_tools.rb CHANGED
@@ -1,3 +1,8 @@
1
1
  module EtcdTools
2
+ module Cli
3
+ end
4
+
5
+ module Watchdog
6
+ end
2
7
 
3
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: etcd-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radek 'blufor' Slavicinsky
@@ -50,10 +50,12 @@ dependencies:
50
50
  - - ! '>='
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.7.8
53
- description: A set of handful CLIE ETCD tools, part of PortAuthority
53
+ description: A set of handful daemons and command-line utils for ETCD (is part of
54
+ PortAuthority)
54
55
  email: radek.slavicinsky@gmail.com
55
56
  executables:
56
57
  - etcd-watchdog-haproxy
58
+ - etcd2yaml
57
59
  - etcd-watchdog-vip
58
60
  - etcd-erb
59
61
  - yaml2etcd
@@ -63,27 +65,25 @@ files:
63
65
  - bin/etcd-erb
64
66
  - bin/etcd-watchdog-haproxy
65
67
  - bin/etcd-watchdog-vip
68
+ - bin/etcd2yaml
66
69
  - 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
70
+ - lib/etcd-tools/cli/etcd2yaml.rb
71
+ - lib/etcd-tools/cli/etcd_erb.rb
72
+ - lib/etcd-tools/cli/yaml2etcd.rb
73
+ - lib/etcd-tools/erb.rb
74
+ - lib/etcd-tools/etcd.rb
72
75
  - lib/etcd-tools/mixins.rb
73
- - lib/etcd-tools/watchdog/config.rb
74
- - lib/etcd-tools/watchdog/etcd.rb
75
76
  - lib/etcd-tools/watchdog/haproxy.rb
76
- - lib/etcd-tools/watchdog/helpers.rb
77
77
  - lib/etcd-tools/watchdog/init.rb
78
- - lib/etcd-tools/watchdog/logger.rb
79
78
  - lib/etcd-tools/watchdog/threads/etcd.rb
80
79
  - lib/etcd-tools/watchdog/threads/icmp.rb
80
+ - lib/etcd-tools/watchdog/util/config.rb
81
+ - lib/etcd-tools/watchdog/util/etcd.rb
82
+ - lib/etcd-tools/watchdog/util/helpers.rb
83
+ - lib/etcd-tools/watchdog/util/logger.rb
81
84
  - 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
85
  - lib/etcd_tools.rb
86
- homepage: http://rubygems.org/gems/etcd-tools
86
+ homepage: https://github.com/blufor/etcd-tools
87
87
  licenses:
88
88
  - GPLv2
89
89
  metadata: {}
@@ -106,5 +106,5 @@ rubyforge_project:
106
106
  rubygems_version: 2.4.3
107
107
  signing_key:
108
108
  specification_version: 4
109
- summary: CLI ETCD tools
109
+ summary: ETCD tools
110
110
  test_files: []
@@ -1,26 +0,0 @@
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
@@ -1,38 +0,0 @@
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
@@ -1,20 +0,0 @@
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
@@ -1,82 +0,0 @@
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 @config[:parameters][:interval]
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
@@ -1,38 +0,0 @@
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
@@ -1,30 +0,0 @@
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
@@ -1,23 +0,0 @@
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
@@ -1,42 +0,0 @@
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
@@ -1,23 +0,0 @@
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
@@ -1,44 +0,0 @@
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