summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.md7
-rw-r--r--extra/rest_client.rb25
-rwxr-xr-xextra/syslog.rb111
-rw-r--r--lib/oxidized/api/rest.rb45
-rw-r--r--lib/oxidized/config/bootstrap.rb4
-rw-r--r--lib/oxidized/config/defaults.rb1
-rw-r--r--lib/oxidized/node.rb6
-rw-r--r--lib/oxidized/nodes.rb10
-rw-r--r--lib/oxidized/worker.rb6
-rwxr-xr-xlib/tst19
10 files changed, 214 insertions, 20 deletions
diff --git a/TODO.md b/TODO.md
index d67a0bc..ff5dc15 100644
--- a/TODO.md
+++ b/TODO.md
@@ -16,7 +16,10 @@ I don't really need it myself, since I don't have platforms where it would be ne
# config
- * save keys as strings, load as symbols?
+ * save keys as strings, load as symbols?
+
+# REST API
+ * figure out if is somehow possible to run rack/puma/sinatra stack with oxidized select()
# other
should it offer cli mass config-pusher? (I think not, I have ideas for such
@@ -31,5 +34,5 @@ use sidekiq? Any benefits?
* rspec tests
-# multiple input methods
+# input method fallback
* ssh, with telnet fallback
diff --git a/extra/rest_client.rb b/extra/rest_client.rb
new file mode 100644
index 0000000..2c58d04
--- /dev/null
+++ b/extra/rest_client.rb
@@ -0,0 +1,25 @@
+module Oxidized
+ class RestClient
+ require 'net/http'
+ require 'json'
+ HOST = 'localhost'
+ PORT = 8888
+
+ class << self
+ def next node, opt={}, host=HOST, port=PORT
+ web = new host, port
+ web.next node, opt
+ end
+ end
+
+ def initialize host=HOST, port=PORT
+ @web = Net::HTTP.new host, port
+ end
+
+ def next node, opt={}
+ data = JSON.dump :node => node, :user => opt[:user], :msg => opt[:msg], :from => opt[:from]
+ @web.put '/nodes/next/' + node.to_s, data
+ end
+
+ end
+end
diff --git a/extra/syslog.rb b/extra/syslog.rb
new file mode 100755
index 0000000..10757bc
--- /dev/null
+++ b/extra/syslog.rb
@@ -0,0 +1,111 @@
+#!/usr/bin/env ruby
+
+# IOS:
+# logging discriminator CFG mnemonics includes CONFIG_I
+# logging host SERVER discriminator CFG
+
+# JunOS:
+# set system syslog host SERVER interactive-commands notice
+# set system syslog host SERVER match "^mgd\[[0-9]+\]: UI_COMMIT: .*"
+
+# sudo setcap 'cap_net_bind_service=+ep' /usr/bin/ruby
+
+# exit if fork ## TODO: proper daemonize
+
+require 'socket'
+require 'resolv'
+require './rest_client'
+
+module Oxidized
+ class SyslogMonitor
+ NAME_MAP = {
+ /(.*)\.ip\.tdc\.net/ => '\1',
+ /(.*)\.ip\.fi/ => '\1',
+ }
+ PORT = 514
+ FILE = 'messages'
+ MSG = {
+ :ios => '%SYS-5-CONFIG_I:',
+ :junos => 'UI_COMMIT:',
+ }
+
+ class << self
+ def udp port=PORT, listen=0
+ io = UDPSocket.new
+ io.bind listen, port
+ new io, :udp
+ end
+ def file syslog_file=FILE
+ io = open syslog_file, 'r'
+ io.seek 0, IO::SEEK_END
+ new io, :file
+ end
+ end
+
+ private
+
+ def initialize io, mode=:udp
+ @mode = mode
+ run io
+ end
+
+ def rest opt
+ Oxidized::RestClient.next opt[:name], :user => opt[:user], :msg => opt[:msg],
+ :from => opt[:from]
+ end
+
+ def ios ip, log, i
+ # TODO: we need to fetch 'ip/name' in mode == :file here
+ user = log[i+5]
+ from = log[-1][1..-2]
+ rest( :user => user, :from => from, :model => 'ios', :ip => ip,
+ :name => getname(ip) )
+ end
+
+ def jnpr ip, log, i
+ # TODO: we need to fetch 'ip/name' in mode == :file here
+ user = log[i+2][1..-2]
+ msg = log[(i+6)..-1].join(' ')[10..-2]
+ msg = nil if msg == 'none'
+ rest( :user => user, :msg => msg, :model => 'jnpr', :ip => ip,
+ :name => getname(ip) )
+ end
+
+ def handle_log log, ip
+ log = log.to_s.split ' '
+ if i = log.index(MSG[:ios])
+ ios ip, log, i
+ elsif i = log.index(MSG[:junos])
+ jnpr ip, log, i
+ end
+ end
+
+ def run io
+ while true
+ log = select [io]
+ log, ip = log.first.first, nil
+ if @mode == :udp
+ log, ip = log.recvfrom_nonblock 2000
+ ip = ip.last
+ else
+ begin
+ log = log.read_nonblock 2000
+ rescue EOFError
+ sleep 1
+ retry
+ end
+ end
+ handle_log log, ip
+ end
+ end
+
+ def getname ip
+ name = (Resolv.getname ip.to_s rescue ip)
+ NAME_MAP.each { |re, sub| name.sub! re, sub }
+ name
+ end
+ end
+end
+
+Oxidized::SyslogMonitor.udp
+#Oxidized::SyslogMonitor.file '/var/log/poop'
diff --git a/lib/oxidized/api/rest.rb b/lib/oxidized/api/rest.rb
index 4a5fbca..9e00837 100644
--- a/lib/oxidized/api/rest.rb
+++ b/lib/oxidized/api/rest.rb
@@ -3,6 +3,14 @@ module Oxidized
require 'json'
module API
class Rest
+ module Helpers
+ def send res, msg='OK', status=200
+ res['Content-Type'] = 'application/json'
+ res.status = status
+ res.body = JSON.dump msg
+ end
+ end
+ include Oxidized::API::Rest::Helpers
def initialize nodes, listen
@nodes = nodes
addr, port = listen.to_s.split ':'
@@ -18,26 +26,49 @@ module Oxidized
end
end
def maps
+ @web.mount '/nodes/next', Next, @nodes
@web.mount_proc '/nodes' do |req, res|
#script_name, #path_info
case req.path_info[1..-1]
# /nodes/reload - reloads list of nodes
when 'reload'
@nodes.load
- res.body = JSON.dump 'OK'
- # /nodes/next/node - moves node to head of queue
- when /next\/(.*)/
- @nodes.next $1
- res.body = JSON.dump 'OK'
+ send res
# /nodes/list - returns list of nodes
when 'list'
- res.body = JSON.dump @nodes.list
+ send res, @nodes.list
# /nodes/show/node - returns data about node
when /show\/(.*)/
- res.body = JSON.dump @nodes.show $1
+ send res, @nodes.show($1)
+ end
+ end
+ end
+
+ # /nodes/next/node - moves node to head of queue
+ class Next < WEBrick::HTTPServlet::AbstractServlet
+ include Oxidized::API::Rest::Helpers
+ def initialize server, nodes
+ super server
+ @nodes = nodes
+ end
+ def do_GET req, res
+ @nodes.next req.path_info[1..-1]
+ send res
+ end
+ def do_PUT req, res
+ node = req.path_info[1..-1]
+ begin
+ opt = JSON.load req.body
+ Log.debug "before: #{@nodes.list}"
+ @nodes.next node, opt
+ Log.debug "after: #{@nodes.list}"
+ send res
+ rescue JSON::ParserError
+ send res, 'broken JSON', 400
end
end
end
+
end
end
end
diff --git a/lib/oxidized/config/bootstrap.rb b/lib/oxidized/config/bootstrap.rb
index 2991fa3..c3ea09d 100644
--- a/lib/oxidized/config/bootstrap.rb
+++ b/lib/oxidized/config/bootstrap.rb
@@ -4,10 +4,10 @@ module Oxidized
CFG.username = 'username'
CFG.password = 'password'
CFG.model = 'junos'
- CFG.interval = 30
+ CFG.interval = 60
CFG.log = File.join Config::Root, 'log'
CFG.debug = false
- CFG.threads = 10
+ CFG.threads = 30
CFG.timeout = 5
CFG.prompt = /^([\w\.\-@]{3,30}[#>]\s?)$/
CFG.rest = 8888
diff --git a/lib/oxidized/config/defaults.rb b/lib/oxidized/config/defaults.rb
index 943d90b..d189ca3 100644
--- a/lib/oxidized/config/defaults.rb
+++ b/lib/oxidized/config/defaults.rb
@@ -1,6 +1,7 @@
module Oxidized
class Config
Root = File.join ENV['HOME'], '.config', 'oxidized'
+ Crash = File.join Root, 'crash'
InputDir = File.join Directory, %w(lib oxidized input)
OutputDir = File.join Directory, %w(lib oxidized output)
ModelDir = File.join Directory, %w(lib oxidized model)
diff --git a/lib/oxidized/node.rb b/lib/oxidized/node.rb
index de04c98..35a5948 100644
--- a/lib/oxidized/node.rb
+++ b/lib/oxidized/node.rb
@@ -4,7 +4,7 @@ module Oxidized
class ModelNotFound < StandardError; end
class Node
attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt
- attr_accessor :last, :running
+ attr_accessor :last, :running, :user, :msg, :from
alias :running? :running
def initialize opt
@name = opt[:name]
@@ -47,6 +47,10 @@ module Oxidized
h
end
+ def reset
+ @user = @msg = @from = nil
+ end
+
private
def resolve_prompt opt
diff --git a/lib/oxidized/nodes.rb b/lib/oxidized/nodes.rb
index 3e58348..e5e87e5 100644
--- a/lib/oxidized/nodes.rb
+++ b/lib/oxidized/nodes.rb
@@ -31,9 +31,15 @@ module Oxidized
delete_at i if i
end
# @param node [String] name of the node moved into the head of array
- def next node
+ def next node, opt={}
+ require 'pp'
n = del node
- put n if n
+ if n
+ n.user = opt['user']
+ n.msg = opt['msg']
+ n.from = opt['from']
+ put n
+ end
end
alias :top :next
# @return [String] node from the head of the array
diff --git a/lib/oxidized/worker.rb b/lib/oxidized/worker.rb
index adbaa0e..6966322 100644
--- a/lib/oxidized/worker.rb
+++ b/lib/oxidized/worker.rb
@@ -23,8 +23,12 @@ module Oxidized
node.last = job
@jobs.duration job.time
if job.status == :success
+ msg = "update #{node.name}"
+ msg += " from #{node.from}" if node.from
+ msg += " with message '#{node.msg}'" if node.msg
node.output.new.update node.name, job.config,
- :msg => "update #{node.name}", :group => node.group
+ :msg => msg, :user => node.user, :group => node.group
+ node.reset
else
Log.warn "#{node.name} status #{job.status}"
end
diff --git a/lib/tst b/lib/tst
index b529653..6b314cc 100755
--- a/lib/tst
+++ b/lib/tst
@@ -1,10 +1,19 @@
#!/usr/bin/env ruby20
$: << '.'
-require 'pry'
-#require 'pp'
-#require 'rubygems'
+require 'pry' if ENV['DEV']
require 'oxidized'
-k = Oxidized.new
-
+begin
+ Oxidized.new
+rescue Exception => e
+ open Oxidized::Config::Crash, 'w' do |file|
+ file.puts '-' * 50
+ file.puts Time.now.utc
+ file.puts '-' * 50
+ file.puts e.backtrace
+ file.puts '-' * 50
+ file.puts caller
+ end
+ raise
+end