lib splitted in more files. many new features (client, server, packet)
This commit is contained in:
parent
2f1c21ff79
commit
476d58d7ec
10
dummy_server.rb
Normal file
10
dummy_server.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
require 'socket'
|
||||||
|
|
||||||
|
module NSCA
|
||||||
|
class ServerDummy
|
||||||
|
attr_reader :server
|
||||||
|
def initialize *host_and_port
|
||||||
|
@server = TCPServer.new *host_and_port
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
313
lib/nsca.rb
313
lib/nsca.rb
|
@ -2,6 +2,7 @@ require 'socket'
|
||||||
require 'enum'
|
require 'enum'
|
||||||
require 'timeout'
|
require 'timeout'
|
||||||
require 'benchmark'
|
require 'benchmark'
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
module NSCA
|
module NSCA
|
||||||
class ReturnCode <Enum
|
class ReturnCode <Enum
|
||||||
|
@ -9,294 +10,6 @@ module NSCA
|
||||||
enum %w[OK WARNING CRITICAL UNKNOWN]
|
enum %w[OK WARNING CRITICAL UNKNOWN]
|
||||||
end
|
end
|
||||||
|
|
||||||
# This class losly based on send_nsca `SendNsca::NscaConnection`-class.
|
|
||||||
class Connection
|
|
||||||
PACKET_VERSION = 3 # NSCA 2.9
|
|
||||||
# packet-version crc32 timestamp return-code hostname service status(incl perfdata) EOT
|
|
||||||
PACK_STRING = "n N N n a64 a128 a4096 C"
|
|
||||||
EOT = 0x17 # Seperator for more than one entry
|
|
||||||
|
|
||||||
attr_reader :xor_key, :timestamp, :socket
|
|
||||||
|
|
||||||
def initialize socket_or_host, port = nil
|
|
||||||
@socket = case socket_or_host
|
|
||||||
when String then Net::TCPSocket.new socket_or_host, port
|
|
||||||
else socket_or_host
|
|
||||||
end
|
|
||||||
# read xor_key and timestamp
|
|
||||||
xor_key_and_timestamp = @socket.recv 132
|
|
||||||
@xor_key, ts = xor_key_and_timestamp.unpack 'a128L'
|
|
||||||
@xor_key_a = @xor_key.unpack 'C*' # needed for every xor
|
|
||||||
@timestamp = Time.at ts
|
|
||||||
end
|
|
||||||
|
|
||||||
def xor msg
|
|
||||||
key_a = @xor_key_a
|
|
||||||
# Slice the message in parts of length key_a.length.
|
|
||||||
# XOR each char of a part with char at the same index in key_a.
|
|
||||||
msg.unpack( 'C*').each_slice( key_a.length).inject do |res, part|
|
|
||||||
res += part.zip( key_a).map {|a,b| a^b }.pack 'C*'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def crc32 msg
|
|
||||||
(msg.each_byte.inject 0xFFFFFFFF do |r,b|
|
|
||||||
8.times.inject( r^b) {|r,_i| (r>>1) ^ (0xEDB88320 * (r&1)) }
|
|
||||||
end) ^ 0xFFFFFFFF
|
|
||||||
end
|
|
||||||
|
|
||||||
# Builds a check-result-line for NSCA.
|
|
||||||
#
|
|
||||||
# Will be terminated by end-of-terminate.
|
|
||||||
# @param [Time,Integer,nil] timestamp Checked at this time
|
|
||||||
# @param [0..3] return_code `NSCA::ReturnCode`
|
|
||||||
# @param [String(length<64),nil] hostname If nil, local hostname will be used.
|
|
||||||
# Must be known by Nagios.
|
|
||||||
# @param [String(length<128)] service Name of Service. Must be known by Nagios.
|
|
||||||
# @param [String(length<4096)] status Status-line inclusive optional Performance Data.
|
|
||||||
def build_package timestamp, return_code, hostname, service, status
|
|
||||||
entry = [
|
|
||||||
PACKET_VERSION, # packet-version
|
|
||||||
0, # crc32 (unknown yet)
|
|
||||||
(timestamp || @timestamp).to_i,
|
|
||||||
return_code.to_i,
|
|
||||||
hostname || `hostname -f`,
|
|
||||||
service,
|
|
||||||
status # incl perfdata
|
|
||||||
]
|
|
||||||
# generate crc32 and put it at entry[2...6]
|
|
||||||
xor "#{entry[0...2]}#{crc32 entry.pack( PACK_STRING)}#{entry[6..-1]}#{EOT.chr}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sends a check-result.
|
|
||||||
# @see #build_package
|
|
||||||
def send( *a) @socket.write build_package( *a) end
|
|
||||||
|
|
||||||
# Sends check-results
|
|
||||||
# @param [Array<NSCA::Check::Base>] results
|
|
||||||
def send_results *results
|
|
||||||
results.flatten.each do |r|
|
|
||||||
send r.timestamp, r.retcode, r.hostname, r.service, r.text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Closes connection to NSCA.
|
|
||||||
def close( *a) @socket.close( *a) end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Server
|
|
||||||
attr_reader :socket_or_host, :port, :connect
|
|
||||||
def initialize socket_or_host = nil, port = nil, &connect
|
|
||||||
@socket_or_host, @port = socket_or_host, port
|
|
||||||
@connect = connect || lambda { Connection.new @socket_or_host, @port }
|
|
||||||
end
|
|
||||||
|
|
||||||
def open &e
|
|
||||||
conn = @connect.call
|
|
||||||
if block_given?
|
|
||||||
begin yield conn
|
|
||||||
ensure conn && conn.close
|
|
||||||
end
|
|
||||||
else
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send *results
|
|
||||||
open do |conn|
|
|
||||||
conn.send_results results
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module PerformanceData
|
|
||||||
class Base
|
|
||||||
extend Timeout
|
|
||||||
extend Benchmark
|
|
||||||
|
|
||||||
def initialize value
|
|
||||||
@value = value
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
attr_reader :label, :unit, :warn, :crit, :min, :max
|
|
||||||
def init label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
|
||||||
@label, @unit, @warn, @crit, @min, @max = label.to_s, unit, warn, crit, min, max
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def measure &block
|
|
||||||
timeout ||= 0
|
|
||||||
exception = Class.new Timeout::Error
|
|
||||||
pd = perfdatas[perfdata_label]
|
|
||||||
timeout = pd.max
|
|
||||||
m = realtime do
|
|
||||||
begin
|
|
||||||
timeout timeout, exception, &block
|
|
||||||
rescue exception
|
|
||||||
end
|
|
||||||
end
|
|
||||||
new m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :value
|
|
||||||
def label() self.label end
|
|
||||||
def unit() self.unit end
|
|
||||||
def warn() self.warn end
|
|
||||||
def crit() self.crit end
|
|
||||||
def min() self.min end
|
|
||||||
def max() self.max end
|
|
||||||
|
|
||||||
def return_code
|
|
||||||
if @value.nil? then 3
|
|
||||||
elsif crit <= @value then 2
|
|
||||||
elsif warn <= @value then 1
|
|
||||||
else 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
"#{label}=#{value}#{unit},#{warn},#{crit},#{min},#{max}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
def create label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
|
||||||
cl = Class.new Base
|
|
||||||
cl.init label, unit, warn, crit, min, max
|
|
||||||
end
|
|
||||||
|
|
||||||
def new label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
|
||||||
cl = create label, unit, warn, crit, min, max
|
|
||||||
clname = NSCA::Helper.class_name_gen label
|
|
||||||
self.const_set clname, cl if clname
|
|
||||||
cl
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Check
|
|
||||||
class Base
|
|
||||||
attr_reader :perfdatas, :return_code, :status, :timestamp
|
|
||||||
def initialize return_code = nil, status = nil, perfdatas = nil
|
|
||||||
@perfdatas = {}
|
|
||||||
init return_code, status, perfdatas, timestamp || Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def init return_code = nil, status = nil, perfdatas = nil, timestamp = nil
|
|
||||||
@return_code = return_code if return_code
|
|
||||||
@status = status if status
|
|
||||||
perfdatas.each &method( :[]) if perfdatas
|
|
||||||
@timestamp = timestamp if timestamp
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def [] perfdata_label
|
|
||||||
pd = @perfdatas[perfdata_label]
|
|
||||||
pd && pd.value
|
|
||||||
end
|
|
||||||
|
|
||||||
def []= perfdata_label, value
|
|
||||||
cl = self.class.perfdatas[perfdata_label]
|
|
||||||
cl ||= PerformanceData::Base.create perfdata_label
|
|
||||||
@perfdatas[perfdata_label] = cl.new value
|
|
||||||
end
|
|
||||||
|
|
||||||
def text
|
|
||||||
r = "#{status || ReturnCode.find(return_code)}"
|
|
||||||
r += " | #{perfdatas.map( &:to_s).join ' '}" unless perfdatas.empty?
|
|
||||||
r
|
|
||||||
end
|
|
||||||
|
|
||||||
def measure perfdata_label, &block
|
|
||||||
@perfdatas[perfdata_label].measure &block
|
|
||||||
end
|
|
||||||
|
|
||||||
def send servers = nil
|
|
||||||
NSCA.send self, servers
|
|
||||||
end
|
|
||||||
|
|
||||||
def ok status = nil, perfdatas = nil
|
|
||||||
init ReturnCode::OK, status, perfdatas
|
|
||||||
send
|
|
||||||
end
|
|
||||||
|
|
||||||
def warning status = nil, perfdatas = nil
|
|
||||||
init ReturnCode::WARNING, status, perfdatas
|
|
||||||
send
|
|
||||||
end
|
|
||||||
alias warn warning
|
|
||||||
|
|
||||||
def critical status = nil, perfdatas = nil
|
|
||||||
init ReturnCode::CRITICAL, status, perfdatas
|
|
||||||
send
|
|
||||||
end
|
|
||||||
alias crit critical
|
|
||||||
|
|
||||||
def unknown status = nil, perfdatas = nil
|
|
||||||
init ReturnCode::UNKNOWN, status, perfdatas
|
|
||||||
send
|
|
||||||
end
|
|
||||||
|
|
||||||
def determine_return_code
|
|
||||||
rc = self.class.perfdatas.map do |label, pdc|
|
|
||||||
pd = @perfdatas[label]
|
|
||||||
if pd
|
|
||||||
pd.return_code
|
|
||||||
else
|
|
||||||
-1
|
|
||||||
end
|
|
||||||
end.max
|
|
||||||
end
|
|
||||||
|
|
||||||
def retcode
|
|
||||||
rc = return_code || determine_return_code
|
|
||||||
(0..3).include?(rc) ? rc : 3
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
attr_reader :service, :hostname, :perfdatas
|
|
||||||
def init service, hostname = nil, perfdatas = nil
|
|
||||||
@service, @hostname, @perfdatas = service, hostname || `hostname -f`, {}
|
|
||||||
perfdatas.each {|pd| @perfdatas[pd.label] = pd }
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def ok status = nil, perfdatas = nil
|
|
||||||
new.ok status, perfdatas
|
|
||||||
end
|
|
||||||
|
|
||||||
def warning status = nil, perfdatas = nil
|
|
||||||
new.warning status, perfdatas
|
|
||||||
end
|
|
||||||
alias warn warning
|
|
||||||
|
|
||||||
def critical status = nil, perfdatas = nil
|
|
||||||
new.warning status, perfdatas
|
|
||||||
end
|
|
||||||
alias crit critical
|
|
||||||
|
|
||||||
def unknown status = nil, perfdatas = nil
|
|
||||||
new.unknown status, perfdatas
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create service, hostname = nil, perfdatas = nil
|
|
||||||
cl = Class.new Base
|
|
||||||
cl.init service, hostname, perfdatas
|
|
||||||
cl
|
|
||||||
end
|
|
||||||
|
|
||||||
def new service, hostname = nil, perfdatas = nil
|
|
||||||
cl = create service, hostname, perfdatas
|
|
||||||
clname = NSCA::Helper.class_name_gen service
|
|
||||||
self.const_set clname, cl if clname
|
|
||||||
cl
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Helper
|
module Helper
|
||||||
class <<self
|
class <<self
|
||||||
def class_name_gen label
|
def class_name_gen label
|
||||||
|
@ -309,24 +22,16 @@ module NSCA
|
||||||
end
|
end
|
||||||
|
|
||||||
class <<self
|
class <<self
|
||||||
def servers() @servers ||= [] end
|
def destinations() @destinations ||= [] end
|
||||||
|
|
||||||
def send results, servers = nil
|
def send *results
|
||||||
Array.wrap( servers || NSCA.servers).each {|server| server.send results }
|
NSCA.destinations.each {|server| server.send *results }
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module Checks
|
|
||||||
class <<self
|
|
||||||
def perfdata cl, *params
|
|
||||||
const_set cl, NSCA::PerformanceData.create( *params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check cl, service, hostname, perfdatas
|
|
||||||
perfdatas.map! {|cl| cl.is_a?( Symbol) ? const_get( cl) : cl }
|
|
||||||
const_set cl, NSCA::Check.create( service, hostname, perfdatas)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'lib/packet'
|
||||||
|
require 'lib/server'
|
||||||
|
require 'lib/client'
|
||||||
|
require 'lib/check'
|
||||||
|
|
167
lib/nsca/check.rb
Normal file
167
lib/nsca/check.rb
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
module NSCA
|
||||||
|
module PerformanceData
|
||||||
|
class Base
|
||||||
|
extend Timeout
|
||||||
|
extend Benchmark
|
||||||
|
|
||||||
|
class <<self
|
||||||
|
attr_reader :label, :unit, :warn, :crit, :min, :max
|
||||||
|
def init label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
||||||
|
@label, @unit, @warn, @crit, @min, @max = label.to_s, unit, warn, crit, min, max
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def measure &block
|
||||||
|
timeout ||= 0
|
||||||
|
exception = Class.new Timeout::Error
|
||||||
|
pd = perfdatas[perfdata_label]
|
||||||
|
timeout = pd.max
|
||||||
|
m = realtime do
|
||||||
|
begin
|
||||||
|
timeout timeout, exception, &block
|
||||||
|
rescue exception
|
||||||
|
end
|
||||||
|
end
|
||||||
|
new m
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :value
|
||||||
|
def initialize( value) @value = value end
|
||||||
|
def label() self.class.label end
|
||||||
|
def unit() self.class.unit end
|
||||||
|
def warn() self.class.warn end
|
||||||
|
def crit() self.class.crit end
|
||||||
|
def min() self.class.min end
|
||||||
|
def max() self.class.max end
|
||||||
|
def to_s() "#{label}=#{value}#{unit},#{warn},#{crit},#{min},#{max}" end
|
||||||
|
|
||||||
|
def return_code
|
||||||
|
if @value.nil? then 3
|
||||||
|
elsif crit <= @value then 2
|
||||||
|
elsif warn <= @value then 1
|
||||||
|
else 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class <<self
|
||||||
|
def new label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
||||||
|
cl = Class.new Base
|
||||||
|
cl.init label, unit, warn, crit, min, max
|
||||||
|
end
|
||||||
|
|
||||||
|
def create label, unit = nil, warn = nil, crit = nil, min = nil, max = nil
|
||||||
|
cl = new label, unit, warn, crit, min, max
|
||||||
|
clname = NSCA::Helper.class_name_gen label
|
||||||
|
self.const_set clname, cl if clname
|
||||||
|
cl
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Check
|
||||||
|
class Base
|
||||||
|
attr_reader :perfdatas, :return_code, :status, :timestamp
|
||||||
|
def initialize return_code = nil, status = nil, perfdatas = nil
|
||||||
|
@perfdatas = {}
|
||||||
|
init return_code, status, perfdatas, timestamp || Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def init return_code = nil, status = nil, perfdatas = nil, timestamp = nil
|
||||||
|
@return_code = return_code if return_code
|
||||||
|
@status = status if status
|
||||||
|
perfdatas.each &method( :[]) if perfdatas
|
||||||
|
@timestamp = timestamp if timestamp
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def [] perfdata_label
|
||||||
|
pd = @perfdatas[perfdata_label]
|
||||||
|
pd && pd.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def []= perfdata_label, value
|
||||||
|
cl = self.class.perfdatas[perfdata_label]
|
||||||
|
cl ||= PerformanceData::Base.new perfdata_label
|
||||||
|
@perfdatas[perfdata_label] = cl.new value
|
||||||
|
end
|
||||||
|
|
||||||
|
def text
|
||||||
|
r = "#{status || ReturnCode.find(return_code)}"
|
||||||
|
r += " | #{perfdatas.map( &:to_s).join ' '}" unless perfdatas.empty?
|
||||||
|
r
|
||||||
|
end
|
||||||
|
|
||||||
|
def measure( perfdata_label, &block) @perfdatas[perfdata_label].measure &block end
|
||||||
|
def send() NSCA::send self end
|
||||||
|
|
||||||
|
def ok status = nil, perfdatas = nil
|
||||||
|
init ReturnCode::OK, status, perfdatas
|
||||||
|
send
|
||||||
|
end
|
||||||
|
|
||||||
|
def warning status = nil, perfdatas = nil
|
||||||
|
init ReturnCode::WARNING, status, perfdatas
|
||||||
|
send
|
||||||
|
end
|
||||||
|
alias warn warning
|
||||||
|
|
||||||
|
def critical status = nil, perfdatas = nil
|
||||||
|
init ReturnCode::CRITICAL, status, perfdatas
|
||||||
|
send
|
||||||
|
end
|
||||||
|
alias crit critical
|
||||||
|
|
||||||
|
def unknown status = nil, perfdatas = nil
|
||||||
|
init ReturnCode::UNKNOWN, status, perfdatas
|
||||||
|
send
|
||||||
|
end
|
||||||
|
|
||||||
|
def determine_return_code
|
||||||
|
self.class.perfdatas.map do |label, pdc|
|
||||||
|
pd = @perfdatas[label]
|
||||||
|
if pd
|
||||||
|
pd.return_code
|
||||||
|
else
|
||||||
|
-1
|
||||||
|
end
|
||||||
|
end.max
|
||||||
|
end
|
||||||
|
|
||||||
|
def retcode
|
||||||
|
rc = return_code || determine_return_code
|
||||||
|
(0..3).include?(rc) ? rc : 3
|
||||||
|
end
|
||||||
|
|
||||||
|
def service() self.class.service end
|
||||||
|
def hostname() self.class.hostname end
|
||||||
|
|
||||||
|
class <<self
|
||||||
|
attr_reader :service, :hostname, :perfdatas
|
||||||
|
def init service, hostname = nil, perfdatas = nil
|
||||||
|
@service, @hostname, @perfdatas = service, hostname || `hostname -f`, {}
|
||||||
|
perfdatas.each {|pd| @perfdatas[pd.label] = pd }
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def ok( status = nil, perfdatas = nil) new.ok status, perfdatas end
|
||||||
|
def warning( status = nil, perfdatas = nil) new.warning status, perfdatas end
|
||||||
|
alias warn warning
|
||||||
|
def critical( status = nil, perfdatas = nil) new.warning status, perfdatas end
|
||||||
|
alias crit critical
|
||||||
|
def unknown( status = nil, perfdatas = nil) new.unknown status, perfdatas end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Checks
|
||||||
|
def perfdata( cl, *params) const_set cl, NSCA::PerformanceData.new( *params) end
|
||||||
|
|
||||||
|
def check cl, service, hostname, perfdatas = nil
|
||||||
|
perfdatas ||= []
|
||||||
|
perfdatas.map! {|cl| cl.is_a?( Symbol) ? const_get( cl) : cl }
|
||||||
|
const_set cl, NSCA::Check.new( service, hostname, perfdatas)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
100
lib/nsca/client.rb
Normal file
100
lib/nsca/client.rb
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
require 'socket'
|
||||||
|
require 'enum'
|
||||||
|
require 'timeout'
|
||||||
|
require 'benchmark'
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
|
module NSCA
|
||||||
|
class Client
|
||||||
|
class Connection
|
||||||
|
attr_reader :iv_key, :timestamp, :socket, :packet_version, :password
|
||||||
|
|
||||||
|
def self.open *args
|
||||||
|
conn = new *args
|
||||||
|
if block_given?
|
||||||
|
begin yield conn
|
||||||
|
ensure conn && conn.close
|
||||||
|
end
|
||||||
|
else conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# opts must be a hash
|
||||||
|
# Connection.new host, port [, opts]
|
||||||
|
# Connection.new socket [, opts]
|
||||||
|
# Connection.new host, opts # need `opts = {port: Port}`!
|
||||||
|
# Connection.new opts # need `opts = {port: Port, hostname: Hostname}`!
|
||||||
|
# Connection.new opts # need `opts = {port: Port, socket: Socket}`!
|
||||||
|
def initialize *args
|
||||||
|
opts = {}
|
||||||
|
opts = args.pop.dup if args.last.is_a? Hash
|
||||||
|
opts[:host] ||= opts[:hostname]
|
||||||
|
opts[:sock] ||= opts[:socket]
|
||||||
|
opts[:pass] ||= opts[:password]
|
||||||
|
|
||||||
|
case args[0]
|
||||||
|
when String
|
||||||
|
opts[:host] = args[0]
|
||||||
|
opts[:port] ||= args[1]
|
||||||
|
when IO
|
||||||
|
opts[:sock] = args[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@socket = if opts[:sock].is_a? IO
|
||||||
|
opts[:sock]
|
||||||
|
elsif opts[:host].is_a? String
|
||||||
|
TCPSocket.new opts[:host], opts[:port]
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Socket or hostname+port expected."
|
||||||
|
end
|
||||||
|
@packet_version = opts[:packet_version] || PacketV3
|
||||||
|
|
||||||
|
# read iv_key and timestamp
|
||||||
|
iv_key_and_timestamp = @socket.recv 132
|
||||||
|
@iv_key, ts = iv_key_and_timestamp.unpack 'a128N'
|
||||||
|
@timestamp = Time.at ts
|
||||||
|
@password = opts[:pass]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Builds a check-result-line for NSCA.
|
||||||
|
#
|
||||||
|
# Will be terminated by end-of-terminate.
|
||||||
|
# @param [Time,Integer,nil] timestamp Checked at this time
|
||||||
|
# @param [0..3] return_code `NSCA::ReturnCode`
|
||||||
|
# @param [String(length<64),nil] hostname If nil, local hostname will be used.
|
||||||
|
# Must be known by Nagios.
|
||||||
|
# @param [String(length<128)] service Name of Service. Must be known by Nagios.
|
||||||
|
# @param [String(length<512)] status Status-line inclusive optional Performance Data.
|
||||||
|
def build_packet timestamp, return_code, hostname, service, status
|
||||||
|
packet = @packet_version.new timestamp || @timestamp, return_code, hostname, service, status
|
||||||
|
packet.build @iv_key
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sends a check-result.
|
||||||
|
# @see #build_packet
|
||||||
|
def send_packet( *a) @socket.write build_packet( *a) end
|
||||||
|
|
||||||
|
# Sends check-results
|
||||||
|
# @param [Array<NSCA::Check::Base>] results
|
||||||
|
def send *results
|
||||||
|
results.flatten.each do |r|
|
||||||
|
send r.timestamp, r.retcode, r.hostname, r.service, r.text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Closes connection to NSCA.
|
||||||
|
def close( *a) @socket.close( *a) end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :socket_or_host, :port, :password
|
||||||
|
def initialize socket_or_host = nil, port = nil, password = nil, &connect
|
||||||
|
@socket_or_host, @port, @password = socket_or_host, port, password
|
||||||
|
end
|
||||||
|
|
||||||
|
def open &e
|
||||||
|
Connection.open @socket_or_host, @port, @password, &e
|
||||||
|
end
|
||||||
|
|
||||||
|
def send( *results) open {|conn| conn.send results } end
|
||||||
|
end
|
||||||
|
end
|
122
lib/nsca/packet.rb
Normal file
122
lib/nsca/packet.rb
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
require 'socket'
|
||||||
|
require 'enum'
|
||||||
|
require 'timeout'
|
||||||
|
require 'benchmark'
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
|
module NSCA
|
||||||
|
class <<self
|
||||||
|
def xor key, msg, key_a = nil
|
||||||
|
key_a ||= key.unpack 'C*'
|
||||||
|
l = key_a.length
|
||||||
|
return msg if l < 1
|
||||||
|
# Slice the message in parts of length key_a.length.
|
||||||
|
# XOR each char of a part with char at the same index in key_a.
|
||||||
|
msg.unpack( 'C*').each_with_index.map {|c,i| c^key_a[i%l] }.pack 'C*'
|
||||||
|
end
|
||||||
|
|
||||||
|
def crc32 msg
|
||||||
|
(msg.each_byte.inject 0xFFFFFFFF do |r,b|
|
||||||
|
8.times.inject( r^b) {|r,_i| (r>>1) ^ (0xEDB88320 * (r&1)) }
|
||||||
|
end) ^ 0xFFFFFFFF
|
||||||
|
end
|
||||||
|
|
||||||
|
# Builds a null terminated, null padded string of length maxlen
|
||||||
|
def str2cstr( str, maxlen = nil)
|
||||||
|
str = str.to_s
|
||||||
|
str = str.to_s[0..(maxlen-2)] if maxlen
|
||||||
|
"#{str}\x00"
|
||||||
|
end
|
||||||
|
def cstr2str( str, maxlen = nil) str[ 0, x.index( ?\0) || ((maxlen||0)-1)] end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Packet
|
||||||
|
class CSC32CheckFailed <Exception
|
||||||
|
end
|
||||||
|
class VersionCheckFailed <Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.versions version = nil
|
||||||
|
@@versions ||= {}
|
||||||
|
version ? @@versions[version] : @@versions
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.register_version( version, klass) versions[version] = klass end
|
||||||
|
|
||||||
|
# @param [Time,Integer,nil] timestamp Checked at this time
|
||||||
|
# @param [0..3] return_code `NSCA::ReturnCode`
|
||||||
|
# @param [String(length<64),nil] hostname If nil, local hostname will be used.
|
||||||
|
# Must be known by Nagios.
|
||||||
|
# @param [String(length<128)] service Name of Service. Must be known by Nagios.
|
||||||
|
# @param [String(length<512)] status Status-line inclusive optional Performance Data.
|
||||||
|
def initialize timestamp, return_code, hostname, service, status
|
||||||
|
@timestamp, @return_code, @hostname, @service, @status =
|
||||||
|
Time.at( timestamp.to_f), return_code, hostname, service, status
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :timestamp, :return_code, :hostname, :service, :status
|
||||||
|
end
|
||||||
|
|
||||||
|
class PacketV3 < Packet
|
||||||
|
NAGIOS_VERSION = 2.7
|
||||||
|
PACKET_VERSION = 3
|
||||||
|
END_OF_TRANSMISSION = ?\x0a
|
||||||
|
HOSTNAME_LENGTH = 64
|
||||||
|
SERVICE_LENGTH = 128
|
||||||
|
STATUS_LENGTH = 512
|
||||||
|
|
||||||
|
# these line describes the data package:
|
||||||
|
# typedef struct data_packet_struct{
|
||||||
|
# int16_t packet_version;
|
||||||
|
# /* xx means, 2 bytes without any data.
|
||||||
|
# * i do not know, why but there are 2 extra bytes without any need and
|
||||||
|
# * no where i can find information how these will be send,
|
||||||
|
# * because these struct does not know it.
|
||||||
|
# */
|
||||||
|
# u_int32_t crc32_value;
|
||||||
|
# u_int32_t timestamp;
|
||||||
|
# int16_t return_code;
|
||||||
|
# char host_name[MAX_HOSTNAME_LENGTH];
|
||||||
|
# char svc_description[MAX_DESCRIPTION_LENGTH];
|
||||||
|
# char plugin_output[MAX_PLUGINOUTPUT_LENGTH];
|
||||||
|
# /* 2 extre xx, too. */
|
||||||
|
# }data_packet;
|
||||||
|
PACK_STRING = "s> xx L> L> s> Z#{HOSTNAME_LENGTH} Z#{SERVICE_LENGTH} Z#{STATUS_LENGTH} xx"
|
||||||
|
PACK_LENGTH = 2+2+4+4+2+HOSTNAME_LENGTH+SERVICE_LENGTH+STATUS_LENGTH+2
|
||||||
|
register_version PACKET_VERSION, self
|
||||||
|
|
||||||
|
# Builds a check-result-line for NSCA.
|
||||||
|
#
|
||||||
|
# Will be terminated by end-of-terminate.
|
||||||
|
def build key = nil, password = nil
|
||||||
|
entry = [
|
||||||
|
PACKET_VERSION,
|
||||||
|
0, # crc32 (unknown yet)
|
||||||
|
(timestamp || Time.now).to_i,
|
||||||
|
return_code.to_i,
|
||||||
|
NSCA::str2cstr( hostname || `hostname -f`, HOSTNAME_LENGTH),
|
||||||
|
NSCA::str2cstr( service, SERVICE_LENGTH),
|
||||||
|
NSCA::str2cstr( status, STATUS_LENGTH) # incl perfdata
|
||||||
|
]
|
||||||
|
# generate crc32 and put it at entry[2...6]
|
||||||
|
entry[1] = NSCA::crc32 entry.pack( PACK_STRING)
|
||||||
|
entry = entry.pack PACK_STRING
|
||||||
|
entry = NSCA::xor key, entry if key
|
||||||
|
entry = NSCA::xor password, entry if password
|
||||||
|
entry
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.parse entry, key = nil, password = nil, no_verification_checks = nil
|
||||||
|
entry = NSCA::xor key, entry if key
|
||||||
|
entry = NSCA::xor password, entry if password
|
||||||
|
p entry: entry
|
||||||
|
ver, crc32sum, *x = entry.unpack( PACK_STRING)
|
||||||
|
raise VersionCheckFailed, "Packet version 3 expected. (recv: #{ver})" \
|
||||||
|
unless no_verification_checks or 3 == ver
|
||||||
|
entry[4..7] = ?\x00*4
|
||||||
|
raise CSC32CheckFailed, "crc32-check failed. packet seems to be broken." \
|
||||||
|
unless no_verification_checks or crc32sum == NSCA::crc32( entry)
|
||||||
|
new *x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
59
lib/nsca/server.rb
Normal file
59
lib/nsca/server.rb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
require 'socket'
|
||||||
|
require 'enum'
|
||||||
|
require 'timeout'
|
||||||
|
require 'benchmark'
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
|
module NSCA
|
||||||
|
class Server
|
||||||
|
attr_reader :iv_key, :server, :packet_version, :password
|
||||||
|
def initialize *args
|
||||||
|
opts = {}
|
||||||
|
opts = args.pop.dup if args.last.is_a? Hash
|
||||||
|
opts[:host] ||= opts[:hostname]
|
||||||
|
opts[:sock] ||= opts[:socket]
|
||||||
|
opts[:pass] ||= opts[:password]
|
||||||
|
|
||||||
|
case args[0]
|
||||||
|
when Integer
|
||||||
|
opts[:port] = args[0]
|
||||||
|
opts[:host] ||= args[1]
|
||||||
|
when IO
|
||||||
|
opts[:sock] = args[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
@packet_version = opts[:packet_version] || PacketV3
|
||||||
|
@iv_key = (opts[:iv_key] || SecureRandom.random_bytes( 128)).to_s
|
||||||
|
raise ArgumentError, "Key must be 128 bytes long" unless 128 == @iv_key.length
|
||||||
|
@password = opts[:pass].to_s
|
||||||
|
@server = if opts[:serv].is_a?( TCPServer) or opts[:serv].is_a?( UNIXServer)
|
||||||
|
opts[:serv]
|
||||||
|
elsif opts[:port].is_a? Integer
|
||||||
|
TCPServer.new *[opts[:port], opts[:host]].compact
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Server or port-number expected"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept() Connection.new @server.accept, self end
|
||||||
|
def close() @server.close end
|
||||||
|
|
||||||
|
class Connection
|
||||||
|
def initialize socket, server
|
||||||
|
@socket, @server = socket, server
|
||||||
|
@iv_key, @password = server.iv_key, server.password
|
||||||
|
@packet_version = server.packet_version
|
||||||
|
@packet_length = @packet_version::PACK_LENGTH
|
||||||
|
@socket.write [@iv_key, Time.now.to_i].pack( 'a* L>')
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch
|
||||||
|
@packet_version.parse read, @iv_key, @password
|
||||||
|
end
|
||||||
|
|
||||||
|
def eof?() @socket.eof? end
|
||||||
|
def read() @socket.read @packet_length end
|
||||||
|
def close() @socket.close end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
12
test/dummy_server.rb
Normal file
12
test/dummy_server.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module NSCA
|
||||||
|
def self.dummy_server port, password = nil, key = nil
|
||||||
|
require 'pathname'
|
||||||
|
load Pathname.new( __FILE__).dirname.join( '..', 'lib', 'nsca.rb').to_s
|
||||||
|
serv = NSCA::Server.new port, password: password, key: key
|
||||||
|
sock = serv.accept
|
||||||
|
sock.fetch
|
||||||
|
ensure
|
||||||
|
sock.close if sock
|
||||||
|
serv.close if serv
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,34 @@
|
||||||
require 'helper'
|
require 'helper'
|
||||||
|
|
||||||
class TestNSCA < Test::Unit::TestCase
|
class TestNSCA < Test::Unit::TestCase
|
||||||
|
class TestChecks
|
||||||
|
extend NSCA::Checks
|
||||||
|
perfdata :PD1, :pd1_in_sec, :s, 10, 20, 0, 30
|
||||||
|
perfdata :PD2, :pd2_in_1, 1, 0.99, 0.98, 0, 1
|
||||||
|
perfdata :PD3, :pd3_count, :c, 3, 5, 0
|
||||||
|
check :T0, 'TestNSCA0', 'uxnags01-sbe.net.mobilkom.at'
|
||||||
|
check :T1, 'TestNSCA1', 'uxnags01-sbe.net.mobilkom.at', [PD1, PD2]
|
||||||
|
check :T2, :TestNSCA2, 'uxnags01-sbe.net.mobilkom.at', [PD1, PD2, PD3]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'our test server' do
|
||||||
|
should 'receive data. NSCA-server should run on localhost 5777. if not, ignore this test. password=abcdefghijkl' do
|
||||||
|
PD1 = TestChecks::PD1
|
||||||
|
PD2 = TestChecks::PD2
|
||||||
|
PD3 = TestChecks::PD3
|
||||||
|
T0 = TestChecks::T0
|
||||||
|
T1 = TestChecks::T1
|
||||||
|
T2 = TestChecks::T2
|
||||||
|
NSCA.destinations << NSCA::Client.new( 'localhost', 5667, 2)
|
||||||
|
NSCA.send TestChecks::T0.new( 1, "0123456789"*51+"AB")
|
||||||
|
|
||||||
|
return
|
||||||
|
pd1 = PD1.new 3
|
||||||
|
pd2 = PD2.new 0.9996
|
||||||
|
pd3 = PD3.new 2
|
||||||
|
NSCA.send TestChecks::T1.new( nil, "Should be OK", [pd1, pd2, pd3])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestNSCA::ReturnCode < Test::Unit::TestCase
|
class TestNSCA::ReturnCode < Test::Unit::TestCase
|
||||||
|
@ -26,19 +54,19 @@ end
|
||||||
|
|
||||||
class TestNSCA::PerformanceData < Test::Unit::TestCase
|
class TestNSCA::PerformanceData < Test::Unit::TestCase
|
||||||
should 'set a subclass for new PerfData-types' do
|
should 'set a subclass for new PerfData-types' do
|
||||||
NSCA::PerformanceData.new 'subclass test'
|
NSCA::PerformanceData.create 'subclass test'
|
||||||
assert_nothing_raised NameError do
|
assert_nothing_raised NameError do
|
||||||
assert NSCA::PerformanceData::Subclass_test, "No subclass created."
|
assert NSCA::PerformanceData::Subclass_test, "No subclass created."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perfdata *a
|
def perfdata *a
|
||||||
NSCA::PerformanceData.create *a
|
NSCA::PerformanceData.new *a
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Created NSCA::PerformanceData-subclasses' do
|
context 'Created NSCA::PerformanceData-subclasses' do
|
||||||
should 'be the same like returned' do
|
should 'be the same like returned' do
|
||||||
cl = NSCA::PerformanceData.new 'returned and subclass the same test'
|
cl = NSCA::PerformanceData.create 'returned and subclass the same test'
|
||||||
assert cl == NSCA::PerformanceData::Returned_and_subclass_the_same_test, 'Classes are not the same.'
|
assert cl == NSCA::PerformanceData::Returned_and_subclass_the_same_test, 'Classes are not the same.'
|
||||||
end
|
end
|
||||||
should 'have a unit if given' do
|
should 'have a unit if given' do
|
||||||
|
@ -57,8 +85,8 @@ class TestNSCA::PerformanceData < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class TestNSCA::Connection < Test::Unit::TestCase
|
class TestNSCA::Client < Test::Unit::TestCase
|
||||||
should '' do
|
should '' do
|
||||||
NSCA::Connection
|
NSCA::Client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue