oxidized 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/Gemfile +3 -0
  4. data/README.md +133 -0
  5. data/Rakefile +46 -0
  6. data/TODO.md +20 -0
  7. data/bin/oxidized +13 -0
  8. data/extra/rest_client.rb +25 -0
  9. data/extra/syslog.rb +110 -0
  10. data/lib/oxidized.rb +6 -0
  11. data/lib/oxidized/cli.rb +42 -0
  12. data/lib/oxidized/config.rb +55 -0
  13. data/lib/oxidized/config/vars.rb +10 -0
  14. data/lib/oxidized/core.rb +41 -0
  15. data/lib/oxidized/input/cli.rb +48 -0
  16. data/lib/oxidized/input/input.rb +19 -0
  17. data/lib/oxidized/input/ssh.rb +111 -0
  18. data/lib/oxidized/input/telnet.rb +145 -0
  19. data/lib/oxidized/job.rb +14 -0
  20. data/lib/oxidized/jobs.rb +24 -0
  21. data/lib/oxidized/log.rb +21 -0
  22. data/lib/oxidized/manager.rb +57 -0
  23. data/lib/oxidized/model/acos.rb +69 -0
  24. data/lib/oxidized/model/aireos.rb +55 -0
  25. data/lib/oxidized/model/aos.rb +38 -0
  26. data/lib/oxidized/model/aos7.rb +58 -0
  27. data/lib/oxidized/model/aosw.rb +43 -0
  28. data/lib/oxidized/model/eos.rb +32 -0
  29. data/lib/oxidized/model/fortios.rb +44 -0
  30. data/lib/oxidized/model/ios.rb +63 -0
  31. data/lib/oxidized/model/iosxr.rb +47 -0
  32. data/lib/oxidized/model/ironware.rb +33 -0
  33. data/lib/oxidized/model/junos.rb +56 -0
  34. data/lib/oxidized/model/model.rb +152 -0
  35. data/lib/oxidized/model/powerconnect.rb +38 -0
  36. data/lib/oxidized/model/procurve.rb +45 -0
  37. data/lib/oxidized/model/timos.rb +45 -0
  38. data/lib/oxidized/node.rb +169 -0
  39. data/lib/oxidized/node/stats.rb +33 -0
  40. data/lib/oxidized/nodes.rb +143 -0
  41. data/lib/oxidized/output/file.rb +42 -0
  42. data/lib/oxidized/output/git.rb +78 -0
  43. data/lib/oxidized/output/output.rb +5 -0
  44. data/lib/oxidized/source/csv.rb +42 -0
  45. data/lib/oxidized/source/source.rb +11 -0
  46. data/lib/oxidized/source/sql.rb +61 -0
  47. data/lib/oxidized/string.rb +13 -0
  48. data/lib/oxidized/worker.rb +54 -0
  49. data/oxidized.gemspec +20 -0
  50. data/spec/nodes_spec.rb +46 -0
  51. metadata +136 -0
@@ -0,0 +1,10 @@
1
+ module Oxidized::Config::Vars
2
+ # convenience method for accessing node, group or global level user variables
3
+ # nil values will be ignored
4
+ def vars name
5
+ r = @node.vars[name] unless @node.vars.nil?
6
+ r ||= Oxidized::CFG.groups[@node.group].vars[name.to_s] if Oxidized::CFG.groups.has_key?(@node.group)
7
+ r ||= Oxidized::CFG.vars[name.to_s] if Oxidized::CFG.vars.has_key?(name)
8
+ r
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ module Oxidized
2
+ require 'oxidized/log'
3
+ require 'oxidized/string'
4
+ require 'oxidized/config'
5
+ require 'oxidized/config/vars'
6
+ require 'oxidized/worker'
7
+ require 'oxidized/nodes'
8
+ require 'oxidized/manager'
9
+ class << self
10
+ def new *args
11
+ Core.new args
12
+ end
13
+ end
14
+
15
+ class Core
16
+ def initialize args
17
+ Oxidized.mgr = Manager.new
18
+ nodes = Nodes.new
19
+ @worker = Worker.new nodes
20
+ if CFG.rest?
21
+ begin
22
+ require 'oxidized/web'
23
+ rescue LoadError
24
+ raise OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web - or disable web support by setting "rest: false" in your configuration'
25
+ end
26
+ @rest = API::Web.new nodes, CFG.rest
27
+ @rest.run
28
+ end
29
+ run
30
+ end
31
+
32
+ private
33
+
34
+ def run
35
+ while true
36
+ @worker.work
37
+ sleep Config::Sleep
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ module Oxidized
2
+ class Input
3
+ module CLI
4
+
5
+ def initialize
6
+ @post_login = []
7
+ @pre_logout = []
8
+ @username, @password, @exec = nil
9
+ end
10
+
11
+ def get
12
+ connect_cli
13
+ d = @node.model.get
14
+ disconnect
15
+ d
16
+ end
17
+
18
+ def connect_cli
19
+ @post_login.each { |command, block| block ? block.call : (cmd command) }
20
+ end
21
+
22
+ def disconnect_cli
23
+ @pre_logout.each { |command, block| block ? block.call : (cmd command, nil) }
24
+ end
25
+
26
+ def post_login _post_login=nil, &block
27
+ unless @exec
28
+ @post_login << [_post_login, block]
29
+ end
30
+ end
31
+
32
+ def pre_logout _pre_logout=nil, &block
33
+ unless @exec
34
+ @pre_logout << [_pre_logout, block]
35
+ end
36
+ end
37
+
38
+ def username re=/^(Username|login)/
39
+ @username or @username = re
40
+ end
41
+
42
+ def password re=/^Password/
43
+ @password or @password = re
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module Oxidized
2
+ class Input
3
+ include Oxidized::Config::Vars
4
+
5
+ RescueFail = {
6
+ :debug => [
7
+ Errno::ECONNREFUSED,
8
+ ],
9
+ :warn => [
10
+ IOError,
11
+ Timeout::Error,
12
+ Errno::ECONNRESET,
13
+ Errno::EHOSTUNREACH,
14
+ Errno::ENETUNREACH,
15
+ Errno::EPIPE,
16
+ ],
17
+ }
18
+ end
19
+ end
@@ -0,0 +1,111 @@
1
+ module Oxidized
2
+ require 'net/ssh'
3
+ require 'timeout'
4
+ require 'oxidized/input/cli'
5
+ class SSH < Input
6
+ RescueFail = {
7
+ :debug => [
8
+ Net::SSH::Disconnect,
9
+ ],
10
+ :warn => [
11
+ RuntimeError,
12
+ Net::SSH::AuthenticationFailed,
13
+ ],
14
+ }
15
+ include Input::CLI
16
+ class NoShell < OxidizedError; end
17
+
18
+ def connect node
19
+ @node = node
20
+ @output = ''
21
+ @node.model.cfg['ssh'].each { |cb| instance_exec(&cb) }
22
+ secure = CFG.input.ssh.secure
23
+ @ssh = Net::SSH.start @node.ip, @node.auth[:username],
24
+ :password => @node.auth[:password], :timeout => CFG.timeout,
25
+ :paranoid => secure
26
+ unless @exec
27
+ shell_open @ssh
28
+ @username ? shell_login : expect(@node.prompt)
29
+ end
30
+ connected?
31
+ end
32
+
33
+ def connected?
34
+ @ssh and not @ssh.closed?
35
+ end
36
+
37
+ def cmd cmd, expect=@node.prompt
38
+ Log.debug "SSH: #{cmd} @ #{@node.name}"
39
+ if @exec
40
+ @ssh.exec! cmd
41
+ else
42
+ cmd_shell(cmd, expect).gsub(/\r\n/, "\n")
43
+ end
44
+ end
45
+
46
+ def send data
47
+ @ses.send_data data
48
+ end
49
+
50
+ def output
51
+ @output
52
+ end
53
+
54
+ private
55
+
56
+ def disconnect
57
+ disconnect_cli
58
+ # if disconnect does not disconnect us, give up after timeout
59
+ Timeout::timeout(CFG.timeout) { @ssh.loop }
60
+ rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError
61
+ ensure
62
+ @ssh.close if not @ssh.closed?
63
+ end
64
+
65
+ def shell_open ssh
66
+ @ses = ssh.open_channel do |ch|
67
+ ch.on_data do |_ch, data|
68
+ @output << data
69
+ @output = @node.model.expects @output
70
+ end
71
+ ch.request_pty do |_ch, success_pty|
72
+ raise NoShell, "Can't get PTY" unless success_pty
73
+ ch.send_channel_request 'shell' do |_ch, success_shell|
74
+ raise NoShell, "Can't get shell" unless success_shell
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ # Cisco WCS has extremely dubious SSH implementation, SSH auth is always
81
+ # success, it always opens shell and then run auth in shell. I guess
82
+ # they'll never support exec() :)
83
+ def shell_login
84
+ expect username
85
+ cmd @node.auth[:username], password
86
+ cmd @node.auth[:password]
87
+ end
88
+
89
+ def exec state=nil
90
+ state == nil ? @exec : (@exec=state)
91
+ end
92
+
93
+ def cmd_shell(cmd, expect_re)
94
+ @output = ''
95
+ @ses.send_data cmd + "\n"
96
+ @ses.process
97
+ expect expect_re if expect_re
98
+ @output
99
+ end
100
+
101
+ def expect regexp
102
+ Timeout::timeout(CFG.timeout) do
103
+ @ssh.loop(0.1) do
104
+ sleep 0.1
105
+ not @output.match regexp
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,145 @@
1
+ module Oxidized
2
+ require 'net/telnet'
3
+ require 'oxidized/input/cli'
4
+ class Telnet < Input
5
+ RescueFail = {}
6
+ include Input::CLI
7
+ attr_reader :telnet
8
+
9
+ def connect node
10
+ @node = node
11
+ @timeout = CFG.timeout
12
+ @node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
13
+ @telnet = Net::Telnet.new 'Host' => @node.ip, 'Timeout' => @timeout,
14
+ 'Model' => @node.model
15
+ expect username
16
+ @telnet.puts @node.auth[:username]
17
+ expect password
18
+ @telnet.puts @node.auth[:password]
19
+ expect @node.prompt
20
+ end
21
+
22
+ def connected?
23
+ @telnet and not @telnet.sock.closed?
24
+ end
25
+
26
+ def cmd cmd, expect=@node.prompt
27
+ Log.debug "Telnet: #{cmd} @#{@node.name}"
28
+ args = { 'String' => cmd }
29
+ args.merge!({ 'Match' => expect, 'Timeout' => @timeout }) if expect
30
+ @telnet.cmd args
31
+ end
32
+
33
+ def send data
34
+ @telnet.write data
35
+ end
36
+
37
+ def output
38
+ @telnet.output
39
+ end
40
+
41
+ private
42
+
43
+ def expect re
44
+ @telnet.waitfor 'Match' => re, 'Timeout' => @timeout
45
+ end
46
+
47
+ def disconnect
48
+ begin
49
+ disconnect_cli
50
+ @telnet.close
51
+ rescue Errno::ECONNRESET
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+
59
+ class Net::Telnet
60
+ ## FIXME: we just need 'line = model.expects line' to handle pager
61
+ ## how to do this, without redefining the whole damn thing
62
+ ## FIXME: we also need output (not sure I'm going to support this)
63
+ attr_reader :output
64
+ def waitfor(options) # :yield: recvdata
65
+ time_out = @options["Timeout"]
66
+ waittime = @options["Waittime"]
67
+ fail_eof = @options["FailEOF"]
68
+ model = @options["Model"]
69
+
70
+ if options.kind_of?(Hash)
71
+ prompt = if options.has_key?("Match")
72
+ options["Match"]
73
+ elsif options.has_key?("Prompt")
74
+ options["Prompt"]
75
+ elsif options.has_key?("String")
76
+ Regexp.new( Regexp.quote(options["String"]) )
77
+ end
78
+ time_out = options["Timeout"] if options.has_key?("Timeout")
79
+ waittime = options["Waittime"] if options.has_key?("Waittime")
80
+ fail_eof = options["FailEOF"] if options.has_key?("FailEOF")
81
+ else
82
+ prompt = options
83
+ end
84
+
85
+ if time_out == false
86
+ time_out = nil
87
+ end
88
+
89
+ line = ''
90
+ buf = ''
91
+ rest = ''
92
+ until(prompt === line and not IO::select([@sock], nil, nil, waittime))
93
+ unless IO::select([@sock], nil, nil, time_out)
94
+ raise TimeoutError, "timed out while waiting for more data"
95
+ end
96
+ begin
97
+ c = @sock.readpartial(1024 * 1024)
98
+ @output = c
99
+ @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
100
+ if @options["Telnetmode"]
101
+ c = rest + c
102
+ if Integer(c.rindex(/#{IAC}#{SE}/no) || 0) <
103
+ Integer(c.rindex(/#{IAC}#{SB}/no) || 0)
104
+ buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)])
105
+ rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1]
106
+ elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
107
+ c.rindex(/\r\z/no)
108
+ buf = preprocess(c[0 ... pt])
109
+ rest = c[pt .. -1]
110
+ else
111
+ buf = preprocess(c)
112
+ rest = ''
113
+ end
114
+ else
115
+ # Not Telnetmode.
116
+ #
117
+ # We cannot use preprocess() on this data, because that
118
+ # method makes some Telnetmode-specific assumptions.
119
+ buf = rest + c
120
+ rest = ''
121
+ unless @options["Binmode"]
122
+ if pt = buf.rindex(/\r\z/no)
123
+ buf = buf[0 ... pt]
124
+ rest = buf[pt .. -1]
125
+ end
126
+ buf.gsub!(/#{EOL}/no, "\n")
127
+ end
128
+ end
129
+ @log.print(buf) if @options.has_key?("Output_log")
130
+ line += buf
131
+ line = model.expects line
132
+ line = yield line if block_given?
133
+ yield buf if block_given?
134
+ rescue EOFError # End of file reached
135
+ raise if fail_eof
136
+ if line == ''
137
+ line = nil
138
+ yield nil if block_given?
139
+ end
140
+ break
141
+ end
142
+ end
143
+ line
144
+ end
145
+ end
@@ -0,0 +1,14 @@
1
+ module Oxidized
2
+ class Job < Thread
3
+ attr_reader :start, :end, :status, :time, :node, :config
4
+ def initialize node
5
+ @node = node
6
+ @start = Time.now.utc
7
+ super do |node|
8
+ @status, @config = node.run
9
+ @end = Time.now.utc
10
+ @time = @end - @start
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module Oxidized
2
+ class Jobs < Array
3
+ attr_accessor :interval, :duration, :max, :want
4
+ def initialize max, interval, nodes
5
+ @max = max
6
+ #@interval = interval * 60
7
+ @interval = interval
8
+ @nodes = nodes
9
+ @duration = 4
10
+ new_count
11
+ super()
12
+ end
13
+ def duration last
14
+ @duration = (@duration + last) / 2
15
+ new_count
16
+ end
17
+ def new_count
18
+ @want = ((@nodes.size * @duration) / @interval).to_i
19
+ @want = 1 if @want < 1
20
+ @want = @nodes.size if @want > @nodes.size
21
+ @want = @max if @want > @max
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module Oxidized
2
+
3
+ begin
4
+ require 'syslog/logger'
5
+ Log = Syslog::Logger.new 'oxidized'
6
+ Log.define_singleton_method(:file=){|arg|}
7
+ rescue LoadError
8
+ # 1.9.3 has no love for syslog
9
+ require 'logger'
10
+ class Logger < Logger
11
+ def initialize target=STDOUT
12
+ super target
13
+ end
14
+ def file= target
15
+ @logdev = LogDevice.new target
16
+ end
17
+ end
18
+ Log = Logger.new
19
+ end
20
+
21
+ end