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.
- 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
|