oxidized 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +41 -0
- data/README.md +64 -35
- data/Rakefile +7 -9
- data/bin/console +9 -0
- data/bin/oxidized +1 -1
- data/extra/nagios_check_failing_nodes.rb +4 -4
- data/extra/oxidized-report-git-commits +80 -0
- data/extra/rvm.oxidized.upstart +18 -0
- data/lib/oxidized.rb +45 -2
- data/lib/oxidized/cli.rb +54 -4
- data/lib/oxidized/config.rb +45 -39
- data/lib/oxidized/config/vars.rb +8 -3
- data/lib/oxidized/core.rb +5 -12
- data/lib/oxidized/hook.rb +4 -4
- data/lib/oxidized/hook/exec.rb +2 -0
- data/lib/oxidized/hook/githubrepo.rb +57 -0
- data/lib/oxidized/input/cli.rb +2 -1
- data/lib/oxidized/input/ftp.rb +3 -3
- data/lib/oxidized/input/ssh.rb +9 -9
- data/lib/oxidized/input/telnet.rb +3 -3
- data/lib/oxidized/job.rb +3 -3
- data/lib/oxidized/model/fortios.rb +1 -1
- data/lib/oxidized/model/junos.rb +8 -10
- data/lib/oxidized/model/model.rb +3 -2
- data/lib/oxidized/model/powerconnect.rb +11 -6
- data/lib/oxidized/model/saos.rb +24 -0
- data/lib/oxidized/node.rb +16 -16
- data/lib/oxidized/nodes.rb +12 -12
- data/lib/oxidized/output/file.rb +15 -3
- data/lib/oxidized/output/git.rb +10 -7
- data/lib/oxidized/source/csv.rb +7 -7
- data/lib/oxidized/source/http.rb +1 -1
- data/lib/oxidized/source/source.rb +1 -1
- data/lib/oxidized/source/sql.rb +7 -7
- data/lib/oxidized/string.rb +1 -1
- data/lib/oxidized/version.rb +3 -0
- data/lib/oxidized/worker.rb +11 -9
- data/oxidized.gemspec +12 -3
- metadata +75 -7
- data/lib/oxidized/log.rb +0 -22
- data/spec/nodes_spec.rb +0 -46
data/lib/oxidized/model/model.rb
CHANGED
@@ -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.
|
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
|
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
|
-
|
53
|
+
skip_blocks = 0
|
51
54
|
cfg.each_line do |line|
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
58
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
135
|
-
[
|
136
|
-
elsif ['username', 'password'].all? {|e|
|
137
|
-
[
|
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
|
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
|
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
|
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
|
data/lib/oxidized/nodes.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Oxidized
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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 =
|
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
|
-
|
21
|
+
Oxidized.logger.error "node %s raised %s with message '%s'" % [node, err.class, err.message]
|
22
22
|
rescue Resolv::ResolvError => err
|
23
|
-
|
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
|
-
|
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
|
data/lib/oxidized/output/file.rb
CHANGED
@@ -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 =
|
8
|
+
@cfg = Oxidized.config.output.file
|
7
9
|
end
|
8
10
|
|
9
11
|
def setup
|
10
12
|
if @cfg.empty?
|
11
|
-
|
12
|
-
|
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
|
data/lib/oxidized/output/git.rb
CHANGED
@@ -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 =
|
13
|
+
@cfg = Oxidized.config.output.git
|
12
14
|
end
|
13
15
|
|
14
16
|
def setup
|
15
17
|
if @cfg.empty?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
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,
|
data/lib/oxidized/source/csv.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module Oxidized
|
2
2
|
class CSV < Source
|
3
3
|
def initialize
|
4
|
-
@cfg =
|
4
|
+
@cfg = Oxidized.config.source.csv
|
5
5
|
super
|
6
6
|
end
|
7
7
|
|
8
8
|
def setup
|
9
9
|
if @cfg.empty?
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
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
|
data/lib/oxidized/source/http.rb
CHANGED
data/lib/oxidized/source/sql.rb
CHANGED
@@ -8,12 +8,12 @@ class SQL < Source
|
|
8
8
|
|
9
9
|
def setup
|
10
10
|
if @cfg.empty?
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 =
|
47
|
+
@cfg = Oxidized.config.source.sql
|
48
48
|
end
|
49
49
|
|
50
50
|
def connect
|
data/lib/oxidized/string.rb
CHANGED
data/lib/oxidized/worker.rb
CHANGED
@@ -4,7 +4,7 @@ module Oxidized
|
|
4
4
|
class Worker
|
5
5
|
def initialize nodes
|
6
6
|
@nodes = nodes
|
7
|
-
@jobs = Jobs.new
|
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
|
-
|
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 +
|
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
|
-
|
42
|
+
output = node.output.new
|
43
|
+
if output.store node.name, job.config,
|
43
44
|
:msg => msg, :user => node.user, :group => node.group
|
44
|
-
|
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 <
|
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
|
-
|
63
|
+
Oxidized.logger.warn msg
|
62
64
|
end
|
63
65
|
rescue NodeNotFound
|
64
|
-
|
66
|
+
Oxidized.logger.warn "#{node.name} not found, removed while collecting?"
|
65
67
|
end
|
66
68
|
|
67
69
|
end
|