oxidized 0.9.0 → 0.10.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile.lock +41 -0
  5. data/README.md +64 -35
  6. data/Rakefile +7 -9
  7. data/bin/console +9 -0
  8. data/bin/oxidized +1 -1
  9. data/extra/nagios_check_failing_nodes.rb +4 -4
  10. data/extra/oxidized-report-git-commits +80 -0
  11. data/extra/rvm.oxidized.upstart +18 -0
  12. data/lib/oxidized.rb +45 -2
  13. data/lib/oxidized/cli.rb +54 -4
  14. data/lib/oxidized/config.rb +45 -39
  15. data/lib/oxidized/config/vars.rb +8 -3
  16. data/lib/oxidized/core.rb +5 -12
  17. data/lib/oxidized/hook.rb +4 -4
  18. data/lib/oxidized/hook/exec.rb +2 -0
  19. data/lib/oxidized/hook/githubrepo.rb +57 -0
  20. data/lib/oxidized/input/cli.rb +2 -1
  21. data/lib/oxidized/input/ftp.rb +3 -3
  22. data/lib/oxidized/input/ssh.rb +9 -9
  23. data/lib/oxidized/input/telnet.rb +3 -3
  24. data/lib/oxidized/job.rb +3 -3
  25. data/lib/oxidized/model/fortios.rb +1 -1
  26. data/lib/oxidized/model/junos.rb +8 -10
  27. data/lib/oxidized/model/model.rb +3 -2
  28. data/lib/oxidized/model/powerconnect.rb +11 -6
  29. data/lib/oxidized/model/saos.rb +24 -0
  30. data/lib/oxidized/node.rb +16 -16
  31. data/lib/oxidized/nodes.rb +12 -12
  32. data/lib/oxidized/output/file.rb +15 -3
  33. data/lib/oxidized/output/git.rb +10 -7
  34. data/lib/oxidized/source/csv.rb +7 -7
  35. data/lib/oxidized/source/http.rb +1 -1
  36. data/lib/oxidized/source/source.rb +1 -1
  37. data/lib/oxidized/source/sql.rb +7 -7
  38. data/lib/oxidized/string.rb +1 -1
  39. data/lib/oxidized/version.rb +3 -0
  40. data/lib/oxidized/worker.rb +11 -9
  41. data/oxidized.gemspec +12 -3
  42. metadata +75 -7
  43. data/lib/oxidized/log.rb +0 -22
  44. data/spec/nodes_spec.rb +0 -46
@@ -11,7 +11,8 @@ module Oxidized
11
11
  klass.instance_variable_set '@cfg', Hash.new { |h,k| h[k] = [] }
12
12
  klass.instance_variable_set '@procs', Hash.new { |h,k| h[k] = [] }
13
13
  klass.instance_variable_set '@expect', []
14
- klass.const_set :CFG, CFG
14
+ klass.instance_variable_set '@comment', nil
15
+ klass.instance_variable_set '@prompt', nil
15
16
  end
16
17
  def comment _comment='# '
17
18
  return @comment if @comment
@@ -78,7 +79,7 @@ module Oxidized
78
79
  attr_accessor :input, :node
79
80
 
80
81
  def cmd string, &block
81
- out = @input.cmd string
82
+ out = "====================== #{string} ======================\n" + @input.cmd(string)
82
83
  return false unless out
83
84
  self.class.cmds[:all].each do |all_block|
84
85
  out = instance_exec Oxidized::String.new(out), string, &all_block
@@ -14,6 +14,9 @@ class PowerConnect < Oxidized::Model
14
14
  end
15
15
 
16
16
  cmd 'show version' do |cfg|
17
+ if (@stackable.nil?)
18
+ @stackable = true if cfg.match /(U|u)nit\s/
19
+ end
17
20
  cfg = cfg.split("\n").select { |line| not line[/Up\sTime/] }
18
21
  comment cfg.join("\n") + "\n"
19
22
  end
@@ -47,15 +50,17 @@ class PowerConnect < Oxidized::Model
47
50
 
48
51
  def clean cfg
49
52
  out = []
50
- skip_block = false
53
+ skip_blocks = 0
51
54
  cfg.each_line do |line|
52
- if line.match /Up\sTime|Temperature|Power Supplies/i
53
- # For 34xx, 35xx, 54xx, 55xx, 62xx and 8024F we should skip this block (terminated by a blank line)
54
- skip_block = true if @model =~ /^(34|35)(24|48)$|^(54|55)(24|48)$|^(62)(24|48)$|^8024$/
55
+ # If this is a stackable switch we should skip this block of information
56
+ if (line.match /Up\sTime|Temperature|Power Suppl(ies|y)|Fans/i and @stackable == true)
57
+ skip_blocks = 1
58
+ # Some switches have another empty line. This is identified by this line having a colon
59
+ skip_blocks = 2 if line.match /:/
55
60
  end
56
61
  # If we have lines to skip do this until we reach and empty line
57
- if skip_block
58
- skip_block = false if /\S/ !~ line
62
+ if skip_blocks > 0
63
+ skip_blocks -= 1 if /\S/ !~ line
59
64
  next
60
65
  end
61
66
  out << line.strip
@@ -0,0 +1,24 @@
1
+ class SAOS < Oxidized::Model
2
+
3
+ # Ciena SAOS switch
4
+ # used for 6.x devices
5
+
6
+ comment '! '
7
+
8
+ cmd :all do |cfg|
9
+ cfg.each_line.to_a[1..-2].join
10
+ end
11
+
12
+ cmd 'configuration show' do |cfg|
13
+ cfg
14
+ end
15
+
16
+ cfg :telnet do
17
+ username /login:/
18
+ password /assword:/
19
+ end
20
+ cfg :telnet do
21
+ post_login 'system shell session set more off'
22
+ pre_logout 'exit'
23
+ end
24
+ end
data/lib/oxidized/node.rb CHANGED
@@ -5,10 +5,13 @@ module Oxidized
5
5
  class MethodNotFound < OxidizedError; end
6
6
  class ModelNotFound < OxidizedError; end
7
7
  class Node
8
- attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last
8
+ attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last, :repo
9
9
  attr_accessor :running, :user, :msg, :from, :stats, :retry
10
10
  alias :running? :running
11
11
  def initialize opt
12
+ if Oxidized.config.debug == true or opt[:debug] == true
13
+ puts 'resolving DNS for %s...' % opt[:name]
14
+ end
12
15
  @name = opt[:name]
13
16
  @ip = IPAddr.new(opt[:ip]).to_s rescue nil
14
17
  @ip ||= Resolv.new.getaddress @name
@@ -21,6 +24,7 @@ module Oxidized
21
24
  @vars = opt[:vars]
22
25
  @stats = Stats.new
23
26
  @retry = 0
27
+ @repo = Oxidized.config.output.git.repo
24
28
 
25
29
  # model instance needs to access node instance
26
30
  @model.node = self
@@ -54,9 +58,7 @@ module Oxidized
54
58
  end
55
59
  end
56
60
  begin
57
- if input.connect self
58
- input.get
59
- end
61
+ input.connect(self) and input.get
60
62
  rescue *rescue_fail.keys => err
61
63
  resc = ''
62
64
  if not level = rescue_fail[err.class]
@@ -64,7 +66,7 @@ module Oxidized
64
66
  level = rescue_fail[resc]
65
67
  resc = " (rescued #{resc})"
66
68
  end
67
- Log.send(level, '%s raised %s%s with msg "%s"' % [self.ip, err.class, resc, err.message])
69
+ Oxidized.logger.send(level, '%s raised %s%s with msg "%s"' % [self.ip, err.class, resc, err.message])
68
70
  return false
69
71
  rescue => err
70
72
  file = Oxidized::Config::Crash + '.' + self.ip.to_s
@@ -74,7 +76,7 @@ module Oxidized
74
76
  fh.puts '-' * 50
75
77
  fh.puts err.backtrace
76
78
  end
77
- Log.error '%s raised %s with msg "%s", %s saved' % [self.ip, err.class, err.message, file]
79
+ Oxidized.logger.error '%s raised %s with msg "%s", %s saved' % [self.ip, err.class, err.message, file]
78
80
  return false
79
81
  end
80
82
  end
@@ -122,19 +124,17 @@ module Oxidized
122
124
  private
123
125
 
124
126
  def resolve_prompt opt
125
- prompt = opt[:prompt]
126
- prompt ||= @model.prompt
127
- prompt ||= CFG.prompt
127
+ opt[:prompt] || @model.prompt || Oxidized.config.prompt
128
128
  end
129
129
 
130
130
  def resolve_auth opt
131
131
  # Resolve configured username/password, give priority to group level configuration
132
132
  # TODO: refactor to use revised behaviour of Asetus
133
133
  cfg_username, cfg_password =
134
- if CFG.groups.has_key?(@group) and ['username', 'password'].all? {|e| CFG.groups[@group].has_key?(e)}
135
- [CFG.groups[@group].username, CFG.groups[@group].password]
136
- elsif ['username', 'password'].all? {|e| CFG.has_key?(e)}
137
- [CFG.username, CFG.password]
134
+ if Oxidized.config.groups.has_key?(@group) and ['username', 'password'].all? {|e| Oxidized.config.groups[@group].has_key?(e)}
135
+ [Oxidized.config.groups[@group].username, Oxidized.config.groups[@group].password]
136
+ elsif ['username', 'password'].all? {|e| Oxidized.config.has_key?(e)}
137
+ [Oxidized.config.username, Oxidized.config.password]
138
138
  else
139
139
  [nil, nil]
140
140
  end
@@ -145,7 +145,7 @@ module Oxidized
145
145
  end
146
146
 
147
147
  def resolve_input opt
148
- inputs = (opt[:input] or CFG.input.default)
148
+ inputs = (opt[:input] or Oxidized.config.input.default)
149
149
  inputs.split(/\s*,\s*/).map do |input|
150
150
  if not Oxidized.mgr.input[input]
151
151
  Oxidized.mgr.add_input input or raise MethodNotFound, "#{input} not found for node #{ip}"
@@ -155,7 +155,7 @@ module Oxidized
155
155
  end
156
156
 
157
157
  def resolve_output opt
158
- output = (opt[:output] or CFG.output.default)
158
+ output = (opt[:output] or Oxidized.config.output.default)
159
159
  if not Oxidized.mgr.output[output]
160
160
  Oxidized.mgr.add_output output or raise MethodNotFound, "#{output} not found for node #{ip}"
161
161
  end
@@ -163,7 +163,7 @@ module Oxidized
163
163
  end
164
164
 
165
165
  def resolve_model opt
166
- model = (opt[:model] or CFG.model)
166
+ model = (opt[:model] or Oxidized.config.model)
167
167
  if not Oxidized.mgr.model[model]
168
168
  Oxidized.mgr.add_model model or raise ModelNotFound, "#{model} not found for node #{ip}"
169
169
  end
@@ -1,15 +1,15 @@
1
1
  module Oxidized
2
- require 'ipaddr'
3
- require 'oxidized/node'
4
- class Oxidized::NotSupported < OxidizedError; end
5
- class Oxidized::NodeNotFound < OxidizedError; end
2
+ require 'ipaddr'
3
+ require 'oxidized/node'
4
+ class Oxidized::NotSupported < OxidizedError; end
5
+ class Oxidized::NodeNotFound < OxidizedError; end
6
6
  class Nodes < Array
7
7
  attr_accessor :source
8
8
  alias :put :unshift
9
9
  def load node_want=nil
10
10
  with_lock do
11
11
  new = []
12
- @source = CFG.source.default
12
+ @source = Oxidized.config.source.default
13
13
  Oxidized.mgr.add_source @source
14
14
  Oxidized.mgr.source[@source].new.load.each do |node|
15
15
  # we want to load specific node(s), not all of them
@@ -18,13 +18,13 @@ module Oxidized
18
18
  _node = Node.new node
19
19
  new.push _node
20
20
  rescue ModelNotFound => err
21
- Log.error "node %s raised %s with message '%s'" % [node, err.class, err.message]
21
+ Oxidized.logger.error "node %s raised %s with message '%s'" % [node, err.class, err.message]
22
22
  rescue Resolv::ResolvError => err
23
- Log.error "node %s is not resolvable, raised %s with message '%s'" % [node, err.class, err.message]
23
+ Oxidized.logger.error "node %s is not resolvable, raised %s with message '%s'" % [node, err.class, err.message]
24
24
  end
25
25
  end
26
26
  size == 0 ? replace(new) : update_nodes(new)
27
- Log.info "Loaded #{size} nodes"
27
+ Oxidized.logger.info "Loaded #{size} nodes"
28
28
  end
29
29
  end
30
30
 
@@ -148,9 +148,9 @@ module Oxidized
148
148
  end
149
149
  end
150
150
  end
151
-
151
+
152
152
  public
153
-
153
+
154
154
  def version node, group
155
155
  with_lock do
156
156
  i = find_node_index node
@@ -159,7 +159,7 @@ module Oxidized
159
159
  output.version node, group
160
160
  end
161
161
  end
162
-
162
+
163
163
  def get_version node, group, oid
164
164
  with_lock do
165
165
  i = find_node_index node
@@ -168,7 +168,7 @@ module Oxidized
168
168
  output.get_version node, group, oid
169
169
  end
170
170
  end
171
-
171
+
172
172
  def get_diff node, group, oid1, oid2
173
173
  with_lock do
174
174
  i = find_node_index node
@@ -2,14 +2,16 @@ module Oxidized
2
2
  class OxidizedFile < Output
3
3
  require 'fileutils'
4
4
 
5
+ attr_reader :commitref
6
+
5
7
  def initialize
6
- @cfg = CFG.output.file
8
+ @cfg = Oxidized.config.output.file
7
9
  end
8
10
 
9
11
  def setup
10
12
  if @cfg.empty?
11
- CFGS.user.output.file.directory = File.join(Config::Root, 'configs')
12
- CFGS.save :user
13
+ Oxidized.asetus.user.output.file.directory = File.join(Config::Root, 'configs')
14
+ Oxidized.asetus.save :user
13
15
  raise NoConfig, 'no output file config, edit ~/.config/oxidized/config'
14
16
  end
15
17
  end
@@ -22,6 +24,7 @@ class OxidizedFile < Output
22
24
  FileUtils.mkdir_p file
23
25
  file = File.join file, node
24
26
  open(file, 'w') { |fh| fh.write outputs.to_cfg }
27
+ @commitref = file
25
28
  end
26
29
 
27
30
  def fetch node, group
@@ -39,5 +42,14 @@ class OxidizedFile < Output
39
42
  end
40
43
  end
41
44
 
45
+ def version node, group
46
+ # not supported
47
+ []
48
+ end
49
+
50
+ def get_version node, group, oid
51
+ 'not supported'
52
+ end
53
+
42
54
  end
43
55
  end
@@ -7,16 +7,18 @@ class Git < Output
7
7
  raise OxidizedError, 'rugged not found: sudo gem install rugged'
8
8
  end
9
9
 
10
+ attr_reader :commitref
11
+
10
12
  def initialize
11
- @cfg = CFG.output.git
13
+ @cfg = Oxidized.config.output.git
12
14
  end
13
15
 
14
16
  def setup
15
17
  if @cfg.empty?
16
- CFGS.user.output.git.user = 'Oxidized'
17
- CFGS.user.output.git.email = 'o@example.com'
18
- CFGS.user.output.git.repo = File.join(Config::Root, 'oxidized.git')
19
- CFGS.save :user
18
+ Oxidized.asetus.user.output.git.user = 'Oxidized'
19
+ Oxidized.asetus.user.output.git.email = 'o@example.com'
20
+ Oxidized.asetus.user.output.git.repo = File.join(Config::Root, 'oxidized.git')
21
+ Oxidized.asetus.save :user
20
22
  raise NoConfig, 'no output git config, edit ~/.config/oxidized/config'
21
23
  end
22
24
  @cfg.repo = File.expand_path @cfg.repo
@@ -27,6 +29,7 @@ class Git < Output
27
29
  @user = (opt[:user] or @cfg.user)
28
30
  @email = (opt[:email] or @cfg.email)
29
31
  @opt = opt
32
+ @commitref = nil
30
33
  repo = @cfg.repo
31
34
 
32
35
  outputs.types.each do |type|
@@ -63,7 +66,7 @@ class Git < Output
63
66
  end
64
67
  end
65
68
 
66
- #give a hash of all oid revision for the givin node, and the date of the commit
69
+ #give a hash of all oid revision for the given node, and the date of the commit
67
70
  def version node, group
68
71
  begin
69
72
  repo = @cfg.repo
@@ -176,7 +179,7 @@ class Git < Output
176
179
  if tree_old != tree_new
177
180
  repo.config['user.name'] = user
178
181
  repo.config['user.email'] = email
179
- Rugged::Commit.create(repo,
182
+ @commitref = Rugged::Commit.create(repo,
180
183
  :tree => index.write_tree(repo),
181
184
  :message => msg,
182
185
  :parents => repo.empty? ? [] : [repo.head.target].compact,
@@ -1,17 +1,17 @@
1
1
  module Oxidized
2
2
  class CSV < Source
3
3
  def initialize
4
- @cfg = CFG.source.csv
4
+ @cfg = Oxidized.config.source.csv
5
5
  super
6
6
  end
7
7
 
8
8
  def setup
9
9
  if @cfg.empty?
10
- CFGS.user.source.csv.file = File.join(Config::Root, 'router.db')
11
- CFGS.user.source.csv.delimiter = /:/
12
- CFGS.user.source.csv.map.name = 0
13
- CFGS.user.source.csv.map.model = 1
14
- CFGS.save :user
10
+ Oxidized.asetus.user.source.csv.file = File.join(Config::Root, 'router.db')
11
+ Oxidized.asetus.user.source.csv.delimiter = /:/
12
+ Oxidized.asetus.user.source.csv.map.name = 0
13
+ Oxidized.asetus.user.source.csv.map.model = 1
14
+ Oxidized.asetus.save :user
15
15
  raise NoConfig, 'no source csv config, edit ~/.config/oxidized/config'
16
16
  end
17
17
  end
@@ -19,7 +19,7 @@ class CSV < Source
19
19
  def load
20
20
  nodes = []
21
21
  open(File.expand_path @cfg.file).each_line do |line|
22
- next if line.match /^\s*#/
22
+ next if line.match(/^\s*#/)
23
23
  data = line.chomp.split @cfg.delimiter
24
24
  next if data.empty?
25
25
  # map node parameters
@@ -1,7 +1,7 @@
1
1
  module Oxidized
2
2
  class HTTP < Source
3
3
  def initialize
4
- @cfg = CFG.source.http
4
+ @cfg = Oxidized.config.source.http
5
5
  super
6
6
  end
7
7
 
@@ -2,7 +2,7 @@ module Oxidized
2
2
  class Source
3
3
  class NoConfig < OxidizedError; end
4
4
  def initialize
5
- @map = (CFG.model_map or {})
5
+ @map = (Oxidized.config.model_map or {})
6
6
  end
7
7
  def map_model model
8
8
  @map.has_key?(model) ? @map[model] : model
@@ -8,12 +8,12 @@ class SQL < Source
8
8
 
9
9
  def setup
10
10
  if @cfg.empty?
11
- CFGS.user.source.sql.adapter = 'sqlite'
12
- CFGS.user.source.sql.database = File.join(Config::Root, 'sqlite.db')
13
- CFGS.user.source.sql.table = 'devices'
14
- CFGS.user.source.sql.map.name = 'name'
15
- CFGS.user.source.sql.map.model = 'rancid'
16
- CFGS.save :user
11
+ Oxidized.asetus.user.source.sql.adapter = 'sqlite'
12
+ Oxidized.asetus.user.source.sql.database = File.join(Config::Root, 'sqlite.db')
13
+ Oxidized.asetus.user.source.sql.table = 'devices'
14
+ Oxidized.asetus.user.source.sql.map.name = 'name'
15
+ Oxidized.asetus.user.source.sql.map.model = 'rancid'
16
+ Oxidized.asetus.save :user
17
17
  raise NoConfig, 'no source sql config, edit ~/.config/oxidized/config'
18
18
  end
19
19
  end
@@ -44,7 +44,7 @@ class SQL < Source
44
44
 
45
45
  def initialize
46
46
  super
47
- @cfg = CFG.source.sql
47
+ @cfg = Oxidized.config.source.sql
48
48
  end
49
49
 
50
50
  def connect
@@ -16,7 +16,7 @@ module Oxidized
16
16
  # sets @cmd and @name unless @name is already set
17
17
  def set_cmd command
18
18
  @cmd = command
19
- @name = @cmd.strip.gsub(/\s+/, '_') if @name == nil
19
+ @name ||= @cmd.strip.gsub(/\s+/, '_')
20
20
  end
21
21
 
22
22
  def initialize str=''
@@ -0,0 +1,3 @@
1
+ module Oxidized
2
+ VERSION = '0.10.0'
3
+ end
@@ -4,7 +4,7 @@ module Oxidized
4
4
  class Worker
5
5
  def initialize nodes
6
6
  @nodes = nodes
7
- @jobs = Jobs.new CFG.threads, CFG.interval, @nodes
7
+ @jobs = Jobs.new(Oxidized.config.threads, Oxidized.config.interval, @nodes)
8
8
  Thread.abort_on_exception = true
9
9
  end
10
10
 
@@ -14,11 +14,11 @@ module Oxidized
14
14
  ended.each { |job| process job }
15
15
  @jobs.work
16
16
  while @jobs.size < @jobs.want
17
- Log.debug "Jobs #{@jobs.size}, Want: #{@jobs.want}"
17
+ Oxidized.logger.debug "Jobs #{@jobs.size}, Want: #{@jobs.want}"
18
18
  # ask for next node in queue non destructive way
19
19
  nextnode = @nodes.first
20
20
  unless nextnode.last.nil?
21
- break if nextnode.last.end + CFG.interval > Time.now.utc
21
+ break if nextnode.last.end + Oxidized.config.interval > Time.now.utc
22
22
  end
23
23
  # shift nodes and get the next node
24
24
  node = @nodes.get
@@ -39,16 +39,18 @@ module Oxidized
39
39
  msg = "update #{node.name}"
40
40
  msg += " from #{node.from}" if node.from
41
41
  msg += " with message '#{node.msg}'" if node.msg
42
- if node.output.new.store node.name, job.config,
42
+ output = node.output.new
43
+ if output.store node.name, job.config,
43
44
  :msg => msg, :user => node.user, :group => node.group
44
- Log.info "Configuration updated for #{node.group}/#{node.name}"
45
+ Oxidized.logger.info "Configuration updated for #{node.group}/#{node.name}"
45
46
  Oxidized.Hooks.handle :post_store, :node => node,
46
- :job => job
47
+ :job => job,
48
+ :commitref => output.commitref
47
49
  end
48
50
  node.reset
49
51
  else
50
52
  msg = "#{node.name} status #{job.status}"
51
- if node.retry < CFG.retries
53
+ if node.retry < Oxidized.config.retries
52
54
  node.retry += 1
53
55
  msg += ", retry attempt #{node.retry}"
54
56
  @nodes.next node.name
@@ -58,10 +60,10 @@ module Oxidized
58
60
  Oxidized.Hooks.handle :node_fail, :node => node,
59
61
  :job => job
60
62
  end
61
- Log.warn msg
63
+ Oxidized.logger.warn msg
62
64
  end
63
65
  rescue NodeNotFound
64
- Log.warn "#{node.name} not found, removed while collecting?"
66
+ Oxidized.logger.warn "#{node.name} not found, removed while collecting?"
65
67
  end
66
68
 
67
69
  end