etcd-tools 0.1.4 → 0.2.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 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