oxidized 0.1.1

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.
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,47 @@
1
+ class IOSXR < Oxidized::Model
2
+
3
+ # IOS XR model #
4
+
5
+ comment '! '
6
+
7
+ cmd :all do |cfg|
8
+ cfg.each_line.to_a[2..-2].join
9
+ end
10
+
11
+ cmd :secret do |cfg|
12
+ cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
13
+ cfg.gsub! /secret (\d+) (\S+).*/, '<secret hidden>'
14
+ cfg
15
+ end
16
+
17
+ cmd 'show inventory' do |cfg|
18
+ comment cfg
19
+ end
20
+
21
+ cmd 'show platform' do |cfg|
22
+ comment cfg
23
+ end
24
+
25
+ cmd 'show running-config' do |cfg|
26
+ cfg = cfg.each_line.to_a[3..-1].join
27
+ cfg
28
+ end
29
+
30
+ cfg :telnet do
31
+ username /^Username:/
32
+ password /^Password:/
33
+ end
34
+
35
+ cfg :telnet, :ssh do
36
+ post_login 'terminal length 0'
37
+ post_login 'terminal width 0'
38
+ if vars :enable
39
+ post_login do
40
+ send "enable\n"
41
+ send vars(:enable) + "\n"
42
+ end
43
+ end
44
+ pre_logout 'exit'
45
+ end
46
+
47
+ end
@@ -0,0 +1,33 @@
1
+ class IronWare < Oxidized::Model
2
+
3
+ comment '! '
4
+
5
+ cmd :all do |cfg|
6
+ cfg.each_line.to_a[1..-3].join
7
+ end
8
+
9
+ cmd 'show running-config' do |cfg|
10
+ cfg = cfg.each_line.to_a[3..-1].join
11
+ cfg
12
+ end
13
+
14
+ cmd 'show version' do |cfg|
15
+ comment cfg
16
+ end
17
+
18
+ cmd 'show chassis' do |cfg|
19
+ cfg.gsub! "\xFF", '' # ugly hack - avoids JSON.dump utf-8 breakage on 1.9..
20
+ comment cfg
21
+ end
22
+
23
+ cfg :telnet do
24
+ username /^Username:/
25
+ password /^Password:/
26
+ end
27
+
28
+ cfg :telnet, :ssh do
29
+ post_login 'terminal length 0'
30
+ pre_logout 'exit'
31
+ end
32
+
33
+ end
@@ -0,0 +1,56 @@
1
+ class JunOS < Oxidized::Model
2
+
3
+ comment '# '
4
+
5
+ def telnet
6
+ @input.class.to_s.match /Telnet/
7
+ end
8
+
9
+ cmd :all do |cfg|
10
+ # we don't need screen-scraping in ssh due to exec
11
+ cfg = cfg.lines.to_a[1..-2].join if telnet
12
+ cfg
13
+ end
14
+
15
+ cmd :secret do |cfg|
16
+ cfg.gsub! /encrypted-password (\S+).*/, '<secret removed>'
17
+ cfg.gsub! /community (\S+) {/, 'community <hidden> {'
18
+ cfg
19
+ end
20
+
21
+ cmd 'show configuration'
22
+
23
+ cmd 'show version' do |cfg|
24
+ @model = $1 if cfg.match /^Model: (\S+)/
25
+ comment cfg
26
+ end
27
+
28
+ post do
29
+ out = ''
30
+ case @model
31
+ when 'mx960'
32
+ out << cmd('show chassis fabric reachability') { |cfg| comment cfg }
33
+ end
34
+ out
35
+ end
36
+
37
+ cmd 'show chassis hardware' do |cfg|
38
+ comment cfg
39
+ end
40
+
41
+ cfg :telnet do
42
+ username /^login:/
43
+ password /^Password:/
44
+ end
45
+
46
+ cfg :ssh do
47
+ exec true # don't run shell, run each command in exec channel
48
+ end
49
+
50
+ cfg :telnet, :ssh do
51
+ post_login 'set cli screen-length 0'
52
+ post_login 'set cli screen-width 0'
53
+ pre_logout 'exit'
54
+ end
55
+
56
+ end
@@ -0,0 +1,152 @@
1
+ module Oxidized
2
+ class Model
3
+ include Oxidized::Config::Vars
4
+
5
+ class << self
6
+ def inherited klass
7
+ klass.instance_variable_set '@cmd', Hash.new { |h,k| h[k] = [] }
8
+ klass.instance_variable_set '@cfg', Hash.new { |h,k| h[k] = [] }
9
+ klass.instance_variable_set '@procs', Hash.new { |h,k| h[k] = [] }
10
+ klass.instance_variable_set '@expect', []
11
+ klass.const_set :CFG, CFG
12
+ end
13
+ def comment _comment='# '
14
+ return @comment if @comment
15
+ @comment = block_given? ? yield : _comment
16
+ end
17
+ def prompt _prompt=nil
18
+ @prompt or @prompt = _prompt
19
+ end
20
+ def cfg *methods, &block
21
+ [methods].flatten.each do |method|
22
+ @cfg[method.to_s] << block
23
+ end
24
+ end
25
+ def cfgs
26
+ @cfg
27
+ end
28
+ def cmd _cmd=nil, &block
29
+ if _cmd.class == Symbol
30
+ @cmd[_cmd] << block
31
+ else
32
+ @cmd[:cmd] << [_cmd, block]
33
+ end
34
+ end
35
+ def cmds
36
+ @cmd
37
+ end
38
+ def expect re, &block
39
+ @expect << [re, block]
40
+ end
41
+ def expects
42
+ @expect
43
+ end
44
+
45
+ # calls the block at the end of the model, prepending the output of the
46
+ # block to the output string
47
+ #
48
+ # @author Saku Ytti <saku@ytti.fi>
49
+ # @since 0.0.39
50
+ # @yield expects block which should return [String]
51
+ # @return [void]
52
+ def pre &block
53
+ @procs[:pre] << block
54
+ end
55
+
56
+ # calls the block at the end of the model, adding the output of the block
57
+ # to the output string
58
+ #
59
+ # @author Saku Ytti <saku@ytti.fi>
60
+ # @since 0.0.39
61
+ # @yield expects block which should return [String]
62
+ # @return [void]
63
+ def post &block
64
+ @procs[:post] << block
65
+ end
66
+
67
+ # @author Saku Ytti <saku@ytti.fi>
68
+ # @since 0.0.39
69
+ # @return [Hash] hash proc procs :pre+:post to be prepended/postfixed to output
70
+ def procs
71
+ @procs
72
+ end
73
+ end
74
+
75
+ attr_accessor :input, :node
76
+
77
+ def cmd string, &block
78
+ out = @input.cmd string
79
+ return false unless out
80
+ out = Oxidized::String.new out
81
+ self.class.cmds[:all].each do |all_block|
82
+ out = instance_exec out, string, &all_block
83
+ end
84
+ if vars :remove_secret
85
+ self.class.cmds[:secret].each do |all_block|
86
+ out = instance_exec out, string, &all_block
87
+ end
88
+ end
89
+ out = instance_exec out, &block if block
90
+ out
91
+ end
92
+
93
+ def output
94
+ @input.output
95
+ end
96
+
97
+ def send data
98
+ @input.send data
99
+ end
100
+
101
+ def expect re, &block
102
+ self.class.expect re, &block
103
+ end
104
+
105
+ def cfg
106
+ self.class.cfgs
107
+ end
108
+
109
+ def prompt
110
+ self.class.prompt
111
+ end
112
+
113
+ def expects data
114
+ self.class.expects.each do |re, cb|
115
+ if data.match re
116
+ if cb.arity == 2
117
+ data = instance_exec [data, re], &cb
118
+ else
119
+ data = instance_exec data, &cb
120
+ end
121
+ end
122
+ end
123
+ data
124
+ end
125
+
126
+ def get
127
+ data, pre = '', ''
128
+ procs = self.class.procs
129
+ self.class.cmds[:cmd].each do |command, block|
130
+ out = cmd command, &block
131
+ return false unless out
132
+ data << out.to_s
133
+ end
134
+ procs[:pre].each do |pre_proc|
135
+ pre << instance_eval(&pre_proc).to_s
136
+ end
137
+ procs[:post].each do |post_proc|
138
+ data << instance_eval(&post_proc).to_s
139
+ end
140
+ pre + data
141
+ end
142
+
143
+ def comment _comment
144
+ data = ''
145
+ _comment.each_line do |line|
146
+ data << self.class.comment << line
147
+ end
148
+ data
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,38 @@
1
+ class PowerConnect < Oxidized::Model
2
+
3
+ prompt /^([\w\s.@-]+[#>]\s?)$/ # allow spaces in hostname..dell does not limit it.. #
4
+
5
+ comment '! '
6
+
7
+ expect /^\s--More--\s+.*$/ do |data, re|
8
+ send ' '
9
+ data.sub re, ''
10
+ end
11
+
12
+ cmd :all do |cfg|
13
+ cfg.each_line.to_a[1..-3].join
14
+ end
15
+
16
+ cmd 'show version' do |cfg|
17
+ comment cfg
18
+ end
19
+
20
+ cmd 'show system' do |cfg|
21
+ cfg = cfg.each_line.take_while { |line| not line.match(/uptime/i) }
22
+ comment cfg
23
+ end
24
+
25
+ cmd 'show running-config'
26
+
27
+ cfg :telnet do
28
+ username /^User( Name)?:/
29
+ password /^\r?Password:/
30
+ end
31
+
32
+ cfg :telnet, :ssh do
33
+ post_login 'terminal datadump'
34
+ post_login 'enable'
35
+ pre_logout 'exit'
36
+ end
37
+
38
+ end
@@ -0,0 +1,45 @@
1
+ class Procurve < Oxidized::Model
2
+
3
+ # FIXME: this is way too unsafe
4
+ prompt /.*?(\w+# ).*/m
5
+
6
+ comment '! '
7
+
8
+ expect /Press any key to continue/ do
9
+ send ' '
10
+ ""
11
+ end
12
+
13
+ cmd :all do |cfg|
14
+ cfg = cfg.each_line.to_a[1..-3].join
15
+ cfg = cfg.gsub /\r/, ''
16
+ new_cfg = ''
17
+ cfg.each_line do |line|
18
+ line.sub! /^\e.*(\e.*)/, '\1' #leave last escape
19
+ line.sub! /\e\[24;1H/, '' #remove last escape, is it always this?
20
+ new_cfg << line
21
+ end
22
+ new_cfg
23
+ end
24
+
25
+ cmd 'show version' do |cfg|
26
+ comment cfg
27
+ end
28
+
29
+ cmd 'show system-information' do |cfg|
30
+ comment cfg
31
+ end
32
+
33
+ cmd 'show running-config'
34
+
35
+ cfg :telnet do
36
+ username /Username:/
37
+ password /Password:/
38
+ end
39
+
40
+ cfg :telnet, :ssh do
41
+ post_login 'no page'
42
+ pre_logout "logout\ny\nn"
43
+ end
44
+
45
+ end
@@ -0,0 +1,45 @@
1
+ class TiMOS < Oxidized::Model
2
+
3
+ # Alcatel-Lucent TiMOS (Timetra)
4
+ # used in SR/ESS/SAS routers
5
+
6
+ comment '# '
7
+
8
+ prompt /^([-\w\.:>\*]+\s?[#>]\s?)$/
9
+
10
+ cmd :all do |cfg, cmdstring|
11
+ new_cfg = comment "COMMAND: #{cmdstring}\n"
12
+ new_cfg << cfg.each_line.to_a[1..-2].join
13
+ end
14
+
15
+ cmd 'show bof' do |cfg|
16
+ comment cfg
17
+ end
18
+
19
+ cmd 'show system information' do |cfg|
20
+ # strip uptime
21
+ cfg.sub! /^System Up Time.*\n/, ''
22
+ comment cfg
23
+ end
24
+
25
+ cmd 'show card state' do |cfg|
26
+ comment cfg
27
+ end
28
+
29
+ cmd 'show boot-messages' do |cfg|
30
+ cfg.gsub! /\r/, ""
31
+ comment cfg
32
+ end
33
+
34
+ cmd 'admin display-config'
35
+
36
+ cfg :telnet do
37
+ username /^Login: /
38
+ password /^Password: /
39
+ end
40
+
41
+ cfg :telnet, :ssh do
42
+ post_login 'environment no more'
43
+ pre_logout 'logout'
44
+ end
45
+ end
@@ -0,0 +1,169 @@
1
+ module Oxidized
2
+ require 'resolv'
3
+ require 'ostruct'
4
+ require_relative 'node/stats'
5
+ class MethodNotFound < OxidizedError; end
6
+ class ModelNotFound < OxidizedError; end
7
+ class Node
8
+ attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last
9
+ attr_accessor :running, :user, :msg, :from, :stats, :retry
10
+ alias :running? :running
11
+ def initialize opt
12
+ @name = opt[:name]
13
+ @ip = Resolv.getaddress @name
14
+ @group = opt[:group]
15
+ @input = resolve_input opt
16
+ @output = resolve_output opt
17
+ @model = resolve_model opt
18
+ @auth = resolve_auth opt
19
+ @prompt = resolve_prompt opt
20
+ @vars = opt[:vars]
21
+ @stats = Stats.new
22
+ @retry = 0
23
+
24
+ # model instance needs to access node instance
25
+ @model.node = self
26
+ end
27
+
28
+ def run
29
+ status, config = :fail, nil
30
+ @input.each do |input|
31
+ @model.input = input = input.new
32
+ if config=run_input(input)
33
+ status = :success
34
+ break
35
+ else
36
+ status = :no_connection
37
+ end
38
+ end
39
+ @model.input = nil
40
+ [status, config]
41
+ end
42
+
43
+ def run_input input
44
+ rescue_fail = {}
45
+ [input.class::RescueFail, input.class.superclass::RescueFail].each do |hash|
46
+ hash.each do |level,errors|
47
+ errors.each do |err|
48
+ rescue_fail[err] = level
49
+ end
50
+ end
51
+ end
52
+ begin
53
+ if input.connect self
54
+ input.get
55
+ end
56
+ rescue *rescue_fail.keys => err
57
+ resc = ''
58
+ if not level = rescue_fail[err.class]
59
+ resc = err.class.ancestors.find{|e|rescue_fail.keys.include? e}
60
+ level = rescue_fail[resc]
61
+ resc = " (rescued #{resc})"
62
+ end
63
+ Log.send(level, '%s raised %s%s with msg "%s"' % [self.ip, err.class, resc, err.message])
64
+ return false
65
+ rescue => err
66
+ file = Oxidized::Config::Crash + '.' + self.ip.to_s
67
+ open file, 'w' do |fh|
68
+ fh.puts Time.now.utc
69
+ fh.puts err.message + ' [' + err.class.to_s + ']'
70
+ fh.puts '-' * 50
71
+ fh.puts err.backtrace
72
+ end
73
+ Log.error '%s raised %s with msg "%s", %s saved' % [self.ip, err.class, err.message, file]
74
+ end
75
+ end
76
+
77
+ def serialize
78
+ h = {
79
+ :name => @name,
80
+ :full_name => @name,
81
+ :ip => @ip,
82
+ :group => @group,
83
+ :model => @model.class.to_s,
84
+ :last => nil,
85
+ :vars => @vars,
86
+ }
87
+ h[:full_name] = [@group, @name].join('/') if @group
88
+ if @last
89
+ h[:last] = {
90
+ :start => @last.start,
91
+ :end => @last.end,
92
+ :status => @last.status,
93
+ :time => @last.time,
94
+ }
95
+ end
96
+ h
97
+ end
98
+
99
+ def last= job
100
+ if job
101
+ ostruct = OpenStruct.new
102
+ ostruct.start = job.start
103
+ ostruct.end = job.end
104
+ ostruct.status = job.status
105
+ ostruct.time = job.time
106
+ @last = ostruct
107
+ else
108
+ @last = nil
109
+ end
110
+ end
111
+
112
+ def reset
113
+ @user = @msg = @from = nil
114
+ @retry = 0
115
+ end
116
+
117
+ private
118
+
119
+ def resolve_prompt opt
120
+ prompt = opt[:prompt]
121
+ prompt ||= @model.prompt
122
+ prompt ||= CFG.prompt
123
+ end
124
+
125
+ def resolve_auth opt
126
+ # Resolve configured username/password, give priority to group level configuration
127
+ # TODO: refactor to use revised behaviour of Asetus
128
+ cfg_username, cfg_password =
129
+ if CFG.groups.has_key?(@group) and ['username', 'password'].all? {|e| CFG.groups[@group].has_key?(e)}
130
+ [CFG.groups[@group].username, CFG.groups[@group].password]
131
+ elsif ['username', 'password'].all? {|e| CFG.has_key?(e)}
132
+ [CFG.username, CFG.password]
133
+ else
134
+ [nil, nil]
135
+ end
136
+ auth = {}
137
+ auth[:username] = (opt[:username] or cfg_username)
138
+ auth[:password] = (opt[:password] or cfg_password)
139
+ auth
140
+ end
141
+
142
+ def resolve_input opt
143
+ inputs = (opt[:input] or CFG.input.default)
144
+ inputs.split(/\s*,\s*/).map do |input|
145
+ if not Oxidized.mgr.input[input]
146
+ Oxidized.mgr.add_input input or raise MethodNotFound, "#{input} not found for node #{ip}"
147
+ end
148
+ Oxidized.mgr.input[input]
149
+ end
150
+ end
151
+
152
+ def resolve_output opt
153
+ output = (opt[:output] or CFG.output.default)
154
+ if not Oxidized.mgr.output[output]
155
+ Oxidized.mgr.add_output output or raise MethodNotFound, "#{output} not found for node #{ip}"
156
+ end
157
+ Oxidized.mgr.output[output]
158
+ end
159
+
160
+ def resolve_model opt
161
+ model = (opt[:model] or CFG.model)
162
+ if not Oxidized.mgr.model[model]
163
+ Oxidized.mgr.add_model model or raise ModelNotFound, "#{model} not found for node #{ip}"
164
+ end
165
+ Oxidized.mgr.model[model].new
166
+ end
167
+
168
+ end
169
+ end