lib splitted in more files. many new features (client, server, packet)
This commit is contained in:
parent
2f1c21ff79
commit
476d58d7ec
8 changed files with 512 additions and 309 deletions
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,300 +2,13 @@ require 'socket'
|
|||
require 'enum'
|
||||
require 'timeout'
|
||||
require 'benchmark'
|
||||
require 'securerandom'
|
||||
|
||||
module NSCA
|
||||
class ReturnCode <Enum
|
||||
start_at 0
|
||||
enum %w[OK WARNING CRITICAL UNKNOWN]
|
||||
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
|
||||
class <<self
|
||||
|
@ -309,24 +22,16 @@ module NSCA
|
|||
end
|
||||
|
||||
class <<self
|
||||
def servers() @servers ||= [] end
|
||||
def destinations() @destinations ||= [] end
|
||||
|
||||
def send results, servers = nil
|
||||
Array.wrap( servers || NSCA.servers).each {|server| server.send results }
|
||||
def send *results
|
||||
NSCA.destinations.each {|server| server.send *results }
|
||||
self
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
|
||||
class TestNSCA::ReturnCode < Test::Unit::TestCase
|
||||
|
@ -26,19 +54,19 @@ end
|
|||
|
||||
class TestNSCA::PerformanceData < Test::Unit::TestCase
|
||||
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 NSCA::PerformanceData::Subclass_test, "No subclass created."
|
||||
end
|
||||
end
|
||||
|
||||
def perfdata *a
|
||||
NSCA::PerformanceData.create *a
|
||||
NSCA::PerformanceData.new *a
|
||||
end
|
||||
|
||||
context 'Created NSCA::PerformanceData-subclasses' 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.'
|
||||
end
|
||||
should 'have a unit if given' do
|
||||
|
@ -57,8 +85,8 @@ class TestNSCA::PerformanceData < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
|
||||
class TestNSCA::Connection < Test::Unit::TestCase
|
||||
class TestNSCA::Client < Test::Unit::TestCase
|
||||
should '' do
|
||||
NSCA::Connection
|
||||
NSCA::Client
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue