diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12efb4f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +libexec/* diff --git a/bin/debug_command.sh b/bin/debug_command.sh new file mode 100755 index 0000000..2ba9003 --- /dev/null +++ b/bin/debug_command.sh @@ -0,0 +1,8 @@ +#!/bin/bash +command=/opt/nagios-custom-plugins/bin/snmp_check_procs.rb +cmd=/tmp/nag.cmd +out=/tmp/nag.out +err=/tmp/nag.err + +echo "${command} $*" > $cmd +eval "./${command} $*" > $out 2> $err diff --git a/bin/rsync_file_age.rb b/bin/rsync_file_age.rb new file mode 100755 index 0000000..01ee48b --- /dev/null +++ b/bin/rsync_file_age.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'rsync_utils') + +include CommandHelper + +@details = < 0 + return NagiosResult::Warning.new(check.warning[:output],warn) if warn > 0 + return NagiosResult::Ok.new(check.ok[:output],0) +end + +begin + @opts.parse! +rescue StandardError => e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] && @options[:path] + print_help("Missing Arguments: hostname and path are required.") +end + +begin + if @options[:verbose] + puts get_files.stdout + else + print_results(check_files) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/rsync_file_size.rb b/bin/rsync_file_size.rb new file mode 100755 index 0000000..3b4e17b --- /dev/null +++ b/bin/rsync_file_size.rb @@ -0,0 +1,110 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'rsync_utils') + +include CommandHelper + +@details = < e + puts "CRITICAL: #{e.message[0,68]}" + exit(2) + end +end + +def nagios_check_size(list) + check = NagiosCheck.new(@options[:warning], @options[:critical]) + files = [] + list.results.each do |file| + if file.is_file? + files << check.compare(file.size_mb) + end + end + crit = files.select {|r| r.kind_of? NagiosResult::Critical }.length + warn = files.select {|r| r.kind_of? NagiosResult::Warning }.length + return NagiosResult::Critical.new(check.critical[:output],crit) if crit > 0 + return NagiosResult::Warning.new(check.warning[:output],warn) if warn > 0 + return NagiosResult::Ok.new(check.ok[:output],0) +end + +begin + @opts.parse! +rescue StandardError => e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] && @options[:path] + print_help("Missing Arguments: hostname and path are required.") +end + +begin + if @options[:verbose] + puts get_files.stdout + else + print_results(check_files) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_cpu.rb b/bin/snmp_cpu.rb new file mode 100755 index 0000000..14cfb5a --- /dev/null +++ b/bin/snmp_cpu.rb @@ -0,0 +1,82 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_cpu') + +include CommandHelper + +@details = < b.used_percent} + nagios = check.compare(max.used_percent) + nagios.message = "Max CPU Utilization at #{nagios.result} %" + nagios +end + +begin + @opts.parse! +rescue StandardError => e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_cpu + else + print_results(check_cpu) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_load.rb b/bin/snmp_load.rb new file mode 100755 index 0000000..4ba5138 --- /dev/null +++ b/bin/snmp_load.rb @@ -0,0 +1,93 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_load') + +include CommandHelper + +@details = < e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +@options[:load] ||= "1" +unless /^(1|5|15)$/.match(@options[:load]) + print_help("Invalid Arguments: load must be 1, 5, 15") +end + +begin + if @options[:verbose] + puts get_load + else + print_results(check_load) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_memory_usage.rb b/bin/snmp_memory_usage.rb new file mode 100755 index 0000000..684fcb6 --- /dev/null +++ b/bin/snmp_memory_usage.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_memory_usage') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_process') + +include CommandHelper + +@details = < 0 + return NagiosResult::Critical.new(check.critical[:output],crit) + end + if warn > 0 + return NagiosResult::Warning.new(check.warning[:output],warn) + end + return NagiosResult::Ok.new(check.ok[:output],0) +end + +begin + @opts.parse! +rescue StandardError => e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_processes + else + nagios = check_memory + nagios.message = "#{nagios.result} processes#{@process_name} consuming #{nagios.threshold} MB memory" + print_results(nagios) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_process.rb b/bin/snmp_process.rb new file mode 100755 index 0000000..b91a921 --- /dev/null +++ b/bin/snmp_process.rb @@ -0,0 +1,107 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_process') + +include CommandHelper + +@details = < e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_processes + else + print_results(check_process) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_storage.rb b/bin/snmp_storage.rb new file mode 100755 index 0000000..91c07bd --- /dev/null +++ b/bin/snmp_storage.rb @@ -0,0 +1,108 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_storage') + +include CommandHelper + +@details = < 1 + nagios = check.compare(nil) + nagios.message = "Multiple matches on label" + else + if @options[:percent] + comp = storage.first.free_percent + display = "%" + else + comp = storage.first.free_mb + display = "M" + end + nagios = check.compare(comp) + nagios.message = "#{nagios.result} #{display} of disk free on #{@options[:label]}" + end + nagios +end + +begin + @opts.parse! +rescue StandardError => e + print_help("Error: #{e.message}") unless e.message == "exit" +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_storage + else + print_results(check_storage) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_time.rb b/bin/snmp_time.rb new file mode 100755 index 0000000..7f98537 --- /dev/null +++ b/bin/snmp_time.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_time') + +include CommandHelper + +@details = < e + print_help("Error: #{e.message}") +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_time + else + print_results(check_time) + end +rescue StandardError => e + print_exception(e) +end + diff --git a/bin/snmp_uptime.rb b/bin/snmp_uptime.rb new file mode 100755 index 0000000..7765dad --- /dev/null +++ b/bin/snmp_uptime.rb @@ -0,0 +1,82 @@ +#!/usr/bin/env ruby +require 'optparse' +require File.join(File.dirname(__FILE__), "..", 'lib', 'command_helper') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_check') +require File.join(File.dirname(__FILE__), "..", 'lib', 'nagios_result') +require File.join(File.dirname(__FILE__), "..", 'lib', 'system_uptime') + +include CommandHelper + +@details = < e + print_help("Error: #{e.message}") +end + +unless @options[:hostname] + print_help("Missing Arguments: hostname is required.") +end + +begin + if @options[:verbose] + puts get_uptime + else + print_results(check_uptime) + end +rescue StandardError => e + print_exception(e) +end diff --git a/etc/database.yml b/etc/database.yml new file mode 100644 index 0000000..e69f411 --- /dev/null +++ b/etc/database.yml @@ -0,0 +1,5 @@ +production: + adapter: mysql + host: localhost + username: root + password: diff --git a/etc/dictionary b/etc/dictionary new file mode 100644 index 0000000..379cd5b --- /dev/null +++ b/etc/dictionary @@ -0,0 +1,207 @@ +# +# This file contains dictionary translations for parsing +# requests and generating responses. All transactions are +# composed of Attribute/Value Pairs. The value of each attribute +# is specified as one of 4 data types. Valid data types are: +# +# string - 0-253 octets +# ipaddr - 4 octets in network byte order +# integer - 32 bit value in big endian order (high byte first) +# date - 32 bit value in big endian order - seconds since +# 00:00:00 GMT, Jan. 1, 1970 +# +# Enumerated values are stored in the user file with dictionary +# VALUE translations for easy administration. +# +# Example: +# +# ATTRIBUTE VALUE +# --------------- ----- +# Framed-Protocol = PPP +# 7 = 1 (integer encoding) +# + + + +# +# Proper names for everything - use this instead of the above +# +ATTRIBUTE User-Name 1 string +ATTRIBUTE User-Password 2 string +ATTRIBUTE CHAP-Password 3 string +ATTRIBUTE NAS-IP-Address 4 ipaddr +ATTRIBUTE NAS-Port 5 integer +ATTRIBUTE Service-Type 6 integer +ATTRIBUTE Framed-Protocol 7 integer +ATTRIBUTE Framed-IP-Address 8 ipaddr +ATTRIBUTE Framed-IP-Netmask 9 ipaddr +ATTRIBUTE Framed-Routing 10 integer +ATTRIBUTE Filter-Id 11 string +ATTRIBUTE Framed-MTU 12 integer +ATTRIBUTE Framed-Compression 13 integer +ATTRIBUTE Login-IP-Host 14 ipaddr +ATTRIBUTE Login-Service 15 integer +ATTRIBUTE Login-TCP-Port 16 integer +ATTRIBUTE Reply-Message 18 string +ATTRIBUTE Callback-Number 19 string +ATTRIBUTE Callback-Id 20 string +ATTRIBUTE Expiration 21 date +ATTRIBUTE Framed-Route 22 string +ATTRIBUTE Framed-IPX-Network 23 ipaddr +ATTRIBUTE State 24 string +ATTRIBUTE Session-Timeout 27 integer +ATTRIBUTE Idle-Timeout 28 integer +ATTRIBUTE Termination-Action 29 integer +ATTRIBUTE Called-Station-Id 30 string +ATTRIBUTE Calling-Station-Id 31 string +ATTRIBUTE Acct-Status-Type 40 integer +ATTRIBUTE Acct-Delay-Time 41 integer +ATTRIBUTE Acct-Input-Octets 42 integer +ATTRIBUTE Acct-Output-Octets 43 integer +ATTRIBUTE Acct-Session-Id 44 string +ATTRIBUTE Acct-Authentic 45 integer +ATTRIBUTE Acct-Session-Time 46 integer +ATTRIBUTE Acct-Terminate-Cause 49 integer +ATTRIBUTE NAS-Port-Type 61 integer +ATTRIBUTE Port-Limit 62 integer + + +# +# Integer Translations +# + +# User Types + +VALUE Service-Type Login-User 1 +VALUE Service-Type Framed-User 2 +VALUE Service-Type Callback-Login-User 3 +VALUE Service-Type Callback-Framed-User 4 +VALUE Service-Type Outbound-User 5 +VALUE Service-Type Administrative-User 6 +VALUE Service-Type NAS-Prompt-User 7 + +# Framed Protocols + +VALUE Framed-Protocol PPP 1 +VALUE Framed-Protocol SLIP 2 + +# Framed Routing Values + +VALUE Framed-Routing None 0 +VALUE Framed-Routing Broadcast 1 +VALUE Framed-Routing Listen 2 +VALUE Framed-Routing Broadcast-Listen 3 + +# Framed Compression Types + +VALUE Framed-Compression None 0 +VALUE Framed-Compression Van-Jacobson-TCP-IP 1 + +# Login Services + +VALUE Login-Service Telnet 0 +VALUE Login-Service Rlogin 1 +VALUE Login-Service TCP-Clear 2 +VALUE Login-Service PortMaster 3 + +# Status Types + +VALUE Acct-Status-Type Start 1 +VALUE Acct-Status-Type Stop 2 + +# Authentication Types + +VALUE Acct-Authentic RADIUS 1 +VALUE Acct-Authentic Local 2 +VALUE Acct-Authentic PowerLink128 100 + +# Termination Options + +VALUE Termination-Action Default 0 +VALUE Termination-Action RADIUS-Request 1 + +# NAS Port Types, available in ComOS 3.3.1 and later + +VALUE NAS-Port-Type Async 0 +VALUE NAS-Port-Type Sync 1 +VALUE NAS-Port-Type ISDN 2 +VALUE NAS-Port-Type ISDN-V120 3 +VALUE NAS-Port-Type ISDN-V110 4 + +# Acct Terminate Causes, available in ComOS 3.3.2 and later + +VALUE Acct-Terminate-Cause User-Request 1 +VALUE Acct-Terminate-Cause Lost-Carrier 2 +VALUE Acct-Terminate-Cause Lost-Service 3 +VALUE Acct-Terminate-Cause Idle-Timeout 4 +VALUE Acct-Terminate-Cause Session-Timeout 5 +VALUE Acct-Terminate-Cause Admin-Reset 6 +VALUE Acct-Terminate-Cause Admin-Reboot 7 +VALUE Acct-Terminate-Cause Port-Error 8 +VALUE Acct-Terminate-Cause NAS-Error 9 +VALUE Acct-Terminate-Cause NAS-Request 10 +VALUE Acct-Terminate-Cause NAS-Reboot 11 +VALUE Acct-Terminate-Cause Port-Unneeded 12 +VALUE Acct-Terminate-Cause Port-Preempted 13 +VALUE Acct-Terminate-Cause Port-Suspended 14 +VALUE Acct-Terminate-Cause Service-Unavailable 15 +VALUE Acct-Terminate-Cause Callback 16 +VALUE Acct-Terminate-Cause User-Error 17 +VALUE Acct-Terminate-Cause Host-Request 18 + + +# +# Obsolete names for backwards compatibility with older users files +# If you want RADIUS accounting logs to use the new names instead of +# these, move this section to the beginning of the dictionary file +# and kill and restart radiusd +# If you don't have a RADIUS 1.16 users file that you're still using, +# you can delete or ignore this section. +# +ATTRIBUTE Client-Id 4 ipaddr +ATTRIBUTE Client-Port-Id 5 integer +ATTRIBUTE User-Service-Type 6 integer +ATTRIBUTE Framed-Address 8 ipaddr +ATTRIBUTE Framed-Netmask 9 ipaddr +ATTRIBUTE Framed-Filter-Id 11 string +ATTRIBUTE Login-Host 14 ipaddr +ATTRIBUTE Login-Port 16 integer +ATTRIBUTE Old-Password 17 string +ATTRIBUTE Port-Message 18 string +ATTRIBUTE Dialback-No 19 string +ATTRIBUTE Dialback-Name 20 string +ATTRIBUTE Challenge-State 24 string +VALUE Service-Type Dialback-Login-User 3 +VALUE Service-Type Dialback-Framed-User 4 +VALUE Service-Type Shell-User 6 +VALUE Framed-Compression Van-Jacobsen-TCP-IP 1 +#VALUE Auth-Type Unix 1 +# +# END of obsolete names for backwards compatibility +# + +# +# Configuration Values +# uncomment out these two lines to turn account expiration on +# + +#VALUE Server-Config Password-Expiration 30 +#VALUE Server-Config Password-Warning 5 + +## +## VENDOR SPECIFIC ATTRIBUTES +## +## The following entries demonstrate the use of VSAs +## + +# cisco-avpair is used for various functions by cisco IOS. Most +# notably, it's used to create VPDN tunnels. +# +VENDORATTR 9 cisco-avpair 1 string + +# This is a fake attribute to demonstrate how to write named-value +# attributes. +VENDORATTR 1 ibm-enum 254 integer +VENDORVALUE 1 ibm-enum value-1 1 +VENDORVALUE 1 ibm-enum value-2 2 +VENDORVALUE 1 ibm-enum value-3 3 diff --git a/etc/snmp.yml b/etc/snmp.yml new file mode 100644 index 0000000..2b2e8d7 --- /dev/null +++ b/etc/snmp.yml @@ -0,0 +1,2 @@ +default: + community: snmpreader \ No newline at end of file diff --git a/lib/command_helper.rb b/lib/command_helper.rb new file mode 100644 index 0000000..bf79d1f --- /dev/null +++ b/lib/command_helper.rb @@ -0,0 +1,58 @@ +module CommandHelper + + def print_ranges + puts <<-EOD +Ranges + + Description: + Ranges are defined as a start and end point on a numeric scale. The + generalized format for a range is: + + [@]start:end + + Ranges are normally considered to be exclusive and the check will fail + when outside the range. If the range starts with @, then the check will + be inclusive and the check will fail when inside the range. If start = 0, + then start and : are not required. If range is in the format of start:, + then end is assumed to be infinity. To specify negative infinity use ~. + + For simple boolean checks, you should specify what you want to fail. For + example, warning = true will fail when the result is true. If warning = + false the check will fail when the result is false. + + Examples: + 10 = alert when < 0 or > 10 | outside range of { 0..10 } + 10: = alert when < 10 | outside range of { 10..Infinity } + ~:10 = alert when > 10 | outside range of { -Infinity..10 } + 10:20 = alert when < 10 OR > 20 | outside range of { 10..20 } + @10:20 = alert when >= 10 AND <= 20 | inside range of { 10..20 } + true = alert when true + false = alert when false + + EOD + exit(3) + end + + def print_details + puts "\n#{@details}\n" + puts "#{@opts}\n" + exit(3) + end + + def print_help(message) + puts "\n#{message}\n\n" + puts "#{@opts}\n" + exit(3) + end + + def print_results(nagios) + puts nagios.output + exit(nagios.code) + end + + def print_exception(exception) + puts "EXCEPTION: #{exception.message[0,68]}" + exit(2) + end + +end diff --git a/lib/mock_file.rb b/lib/mock_file.rb new file mode 100644 index 0000000..1a40210 --- /dev/null +++ b/lib/mock_file.rb @@ -0,0 +1,38 @@ +class MockFile + + attr_reader :name, :type, :time, :size + + def initialize(name,type,time,size) + @name = name + @time = time + @size = size + @type = type + end + + def is_file? + @type == "File" + end + + def is_dir? + @type == "Directory" + end + + def is_module? + @type == "Module" + end + + def size_mb + @size_mb ||= (size / 1024 / 1024) + end + + def to_s + < [], :output => okout } + end + + def compare(result) + unless do_compare(@critical,result) + return NagiosResult::Critical.new(@critical[:output],result) + end + unless do_compare(@warning,result) + return NagiosResult::Warning.new(@warning[:output],result) + end + return NagiosResult::Ok.new(@ok[:output],result) + end + + private + +# 10 = fail when < 0 or > 10 | outside range of { 0..10 } +# 10: = fail when < 10 | outside range of { 10..Infinity } +# ~:10 = fail when > 10 | outside range of { -Infinity..10 } +# 10:20 = fail when < 10 OR > 20 | outside range of { 10..20 } +# @10:20 = fail when >= 10 AND <= 20 | inside range of { 10..20 } +# true = fail when true +# false = fail when false + def load_check(param) + args = [] + output = nil + param = param.strip if param + case param + when /^\d+$/ + args << [0,param.to_f] + output = "(lt 0 OR gt #{param})" + when /^\d+:$/ + arg = param.split(/:/)[0] + args << [arg.to_f,INFINITY] + output = "(lt #{arg})" + when /^~:\d+$/ + arg = param.split(/:/)[1] + args << [NEGATIVE_INFINITY,arg.to_f] + output = "(gt #{arg})" + when /^\d+:\d+$/ + arg = param.split(/:/) + args << [arg[0].to_f,arg[1].to_f] + output = "(lt #{arg[0]} OR gt #{arg[1]})" + when /^@\d+:\d+$/ + arg = param.split(/:/) + arg[0] = arg[0].sub(/@/,'') + args << [NEGATIVE_INFINITY,arg[0].to_f - 1] + args << [arg[1].to_f + 1,INFINITY] + output = "(gt= #{arg[0]} AND lt= #{arg[1]})" + when "true" + args << [false] + output = "(== true)" + when "false" + args << [true] + output = "(== false)" + end + check = {:thresholds => args, :output => output} + end + + def do_compare(check,result) + count = 0 + count = 1 if check[:thresholds].size == 1 + check[:thresholds].each do |arg| + case result + when nil + return false + when false + return result == arg[0] + when true + return result == arg[0] + else + if(result.to_f < arg[0] or result.to_f > arg[1]) + count = count + 1 + end + end + end + return false if count == 2 + return true + end + +end + + + diff --git a/lib/nagios_result.rb b/lib/nagios_result.rb new file mode 100644 index 0000000..ed20d99 --- /dev/null +++ b/lib/nagios_result.rb @@ -0,0 +1,71 @@ +module NagiosResult + + attr_reader :threshold, :result, :state, :code + attr_accessor :message + + def to_xml + xml = ::Builder::XmlMarkup.new(:indent=>2) + xml.result do + xml.threshold threshold + xml.result result + xml.state state + xml.code code + xml.message message + end + end + + def output + "#{state}: #{message}" + end + + alias to_s output + +end + +module NagiosResult + + class Ok + + include NagiosResult + + def initialize(threshold=nil,result=nil) + @threshold, @result = threshold, result + @state = "OK" + @code = 0 + end + end + + class Warning + + include NagiosResult + + def initialize(threshold=nil,result=nil) + @threshold, @result = threshold, result + @state = "WARNING" + @code = 1 + end + end + + class Critical + + include NagiosResult + + def initialize(threshold=nil,result=nil) + @threshold, @result = threshold, result + @state = "CRITICAL" + @code = 2 + end + end + + class Unknown + + include NagiosResult + + def initialize(threshold=nil,result=nil) + @threshold, @result = threshold, result + @state = "UNKNOWN" + @code = 3 + end + end + +end diff --git a/lib/open-uri.rb b/lib/open-uri.rb new file mode 100644 index 0000000..a4fbf49 --- /dev/null +++ b/lib/open-uri.rb @@ -0,0 +1,693 @@ +require 'uri' +require 'stringio' +require 'time' + +module Kernel + private + alias open_uri_original_open open # :nodoc: + + # makes possible to open various resources including URIs. + # If the first argument respond to `open' method, + # the method is called with the rest arguments. + # + # If the first argument is a string which begins with xxx://, + # it is parsed by URI.parse. If the parsed object respond to `open' method, + # the method is called with the rest arguments. + # + # Otherwise original open is called. + # + # Since open-uri.rb provides URI::HTTP#open, URI::HTTPS#open and + # URI::FTP#open, + # Kernel[#.]open can accepts such URIs and strings which begins with + # http://, https:// and ftp://. + # In these case, the opened file object is extended by OpenURI::Meta. + def open(name, *rest, &block) # :doc: + if name.respond_to?(:open) + name.open(*rest, &block) + elsif name.respond_to?(:to_str) && + %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name && + (uri = URI.parse(name)).respond_to?(:open) + uri.open(*rest, &block) + else + open_uri_original_open(name, *rest, &block) + end + end + module_function :open +end + +# OpenURI is an easy-to-use wrapper for net/http, net/https and net/ftp. +# +#== Example +# +# It is possible to open http/https/ftp URL as usual like opening a file: +# +# open("http://www.ruby-lang.org/") {|f| +# f.each_line {|line| p line} +# } +# +# The opened file has several methods for meta information as follows since +# it is extended by OpenURI::Meta. +# +# open("http://www.ruby-lang.org/en") {|f| +# f.each_line {|line| p line} +# p f.base_uri # +# p f.content_type # "text/html" +# p f.charset # "iso-8859-1" +# p f.content_encoding # [] +# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002 +# } +# +# Additional header fields can be specified by an optional hash argument. +# +# open("http://www.ruby-lang.org/en/", +# "User-Agent" => "Ruby/#{RUBY_VERSION}", +# "From" => "foo@bar.invalid", +# "Referer" => "http://www.ruby-lang.org/") {|f| +# # ... +# } +# +# The environment variables such as http_proxy, https_proxy and ftp_proxy +# are in effect by default. :proxy => nil disables proxy. +# +# open("http://www.ruby-lang.org/en/raa.html", :proxy => nil) {|f| +# # ... +# } +# +# URI objects can be opened in a similar way. +# +# uri = URI.parse("http://www.ruby-lang.org/en/") +# uri.open {|f| +# # ... +# } +# +# URI objects can be read directly. The returned string is also extended by +# OpenURI::Meta. +# +# str = uri.read +# p str.base_uri +# +# Author:: Tanaka Akira + +module OpenURI + + OPEN_TIMEOUT_DEFAULT = 10 + READ_TIMEOUT_DEFAULT = 20 + + Options = { + :proxy => true, + :progress_proc => true, + :content_length_proc => true, + :http_basic_authentication => true, + :ssl_verify => true, + :open_timeout => true, + :read_timeout => true + } + + def OpenURI.check_options(options) # :nodoc: + options.each {|k, v| + next unless Symbol === k + unless Options.include? k + raise ArgumentError, "unrecognized option: #{k}" + end + } + end + + def OpenURI.scan_open_optional_arguments(*rest) # :nodoc: + if !rest.empty? && (String === rest.first || Integer === rest.first) + mode = rest.shift + if !rest.empty? && Integer === rest.first + perm = rest.shift + end + end + return mode, perm, rest + end + + def OpenURI.open_uri(name, *rest) # :nodoc: + uri = URI::Generic === name ? name : URI.parse(name) + mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest) + options = rest.shift if !rest.empty? && Hash === rest.first + raise ArgumentError.new("extra arguments") if !rest.empty? + options ||= {} + OpenURI.check_options(options) + + unless mode == nil || + mode == 'r' || mode == 'rb' || + mode == File::RDONLY + raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)") + end + + io = open_loop(uri, options) + if block_given? + begin + yield io + ensure + io.close + end + else + io + end + end + + def OpenURI.open_loop(uri, options) # :nodoc: + case opt_proxy = options.fetch(:proxy, true) + when true + find_proxy = lambda {|u| u.find_proxy} + when nil, false + find_proxy = lambda {|u| nil} + when String + opt_proxy = URI.parse(opt_proxy) + find_proxy = lambda {|u| opt_proxy} + when URI::Generic + find_proxy = lambda {|u| opt_proxy} + else + raise ArgumentError.new("Invalid proxy option: #{opt_proxy}") + end + + uri_set = {} + buf = nil + while true + redirect = catch(:open_uri_redirect) { + buf = Buffer.new + uri.buffer_open(buf, find_proxy.call(uri), options) + nil + } + if redirect + if redirect.relative? + # Although it violates RFC2616, Location: field may have relative + # URI. It is converted to absolute URI using uri as a base URI. + redirect = uri + redirect + end + unless OpenURI.redirectable?(uri, redirect) + raise "redirection forbidden: #{uri} -> #{redirect}" + end + if options.include? :http_basic_authentication + # send authentication only for the URI directly specified. + options = options.dup + options.delete :http_basic_authentication + end + uri = redirect + raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s + uri_set[uri.to_s] = true + else + break + end + end + io = buf.io + io.base_uri = uri + io + end + + def OpenURI.redirectable?(uri1, uri2) # :nodoc: + # This test is intended to forbid a redirection from http://... to + # file:///etc/passwd. + # However this is ad hoc. It should be extensible/configurable. + uri1.scheme.downcase == uri2.scheme.downcase || + (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme) + end + + def OpenURI.open_http(buf, target, proxy, options) # :nodoc: + if proxy + raise "Non-HTTP proxy URI: #{proxy}" if proxy.class != URI::HTTP + end + + if target.userinfo && "1.9.0" <= RUBY_VERSION + # don't raise for 1.8 because compatibility. + raise ArgumentError, "userinfo not supported. [RFC3986]" + end + + require 'net/http' + klass = Net::HTTP + if URI::HTTP === target + # HTTP or HTTPS + if proxy + klass = Net::HTTP::Proxy(proxy.host, proxy.port) + end + target_host = target.host + target_port = target.port + request_uri = target.request_uri + else + # FTP over HTTP proxy + target_host = proxy.host + target_port = proxy.port + request_uri = target.to_s + end + + http = klass.new(target_host, target_port) + if target.class == URI::HTTPS + require 'net/https' + http.use_ssl = true + http.enable_post_connection_check = true + if options[:ssl_verify] == false + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + else + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + store = OpenSSL::X509::Store.new + store.set_default_paths + http.cert_store = store + end + + http.open_timeout = options[:open_timeout] || OPEN_TIMEOUT_DEFAULT + http.read_timeout = options[:read_timeout] || READ_TIMEOUT_DEFAULT + + header = {} + options.each {|k, v| header[k] = v if String === k } + + resp = nil + http.start { + req = Net::HTTP::Get.new(request_uri, header) + if options.include? :http_basic_authentication + user, pass = options[:http_basic_authentication] + req.basic_auth user, pass + end + http.request(req) {|response| + resp = response + if options[:content_length_proc] && Net::HTTPSuccess === resp + if resp.key?('Content-Length') + options[:content_length_proc].call(resp['Content-Length'].to_i) + else + options[:content_length_proc].call(nil) + end + end + resp.read_body {|str| + buf << str + if options[:progress_proc] && Net::HTTPSuccess === resp + options[:progress_proc].call(buf.size) + end + } + } + } + io = buf.io + io.rewind + io.status = [resp.code, resp.message] + resp.each {|name,value| buf.io.meta_add_field name, value } + case resp + when Net::HTTPSuccess + when Net::HTTPMovedPermanently, # 301 + Net::HTTPFound, # 302 + Net::HTTPSeeOther, # 303 + Net::HTTPTemporaryRedirect # 307 + throw :open_uri_redirect, URI.parse(resp['location']) + else + raise OpenURI::HTTPError.new(io.status.join(' '), io) + end + end + + class HTTPError < StandardError + def initialize(message, io) + super(message) + @io = io + end + attr_reader :io + end + + class Buffer # :nodoc: + def initialize + @io = StringIO.new + @size = 0 + end + attr_reader :size + + StringMax = 10240 + def <<(str) + @io << str + @size += str.length + if StringIO === @io && StringMax < @size + require 'tempfile' + io = Tempfile.new('open-uri') + io.binmode + Meta.init io, @io if @io.respond_to? :meta + io << @io.string + @io = io + end + end + + def io + Meta.init @io unless @io.respond_to? :meta + @io + end + end + + # Mixin for holding meta-information. + module Meta + def Meta.init(obj, src=nil) # :nodoc: + obj.extend Meta + obj.instance_eval { + @base_uri = nil + @meta = {} + } + if src + obj.status = src.status + obj.base_uri = src.base_uri + src.meta.each {|name, value| + obj.meta_add_field(name, value) + } + end + end + + # returns an Array which consists status code and message. + attr_accessor :status + + # returns a URI which is base of relative URIs in the data. + # It may differ from the URI supplied by a user because redirection. + attr_accessor :base_uri + + # returns a Hash which represents header fields. + # The Hash keys are downcased for canonicalization. + attr_reader :meta + + def meta_add_field(name, value) # :nodoc: + @meta[name.downcase] = value + end + + # returns a Time which represents Last-Modified field. + def last_modified + if v = @meta['last-modified'] + Time.httpdate(v) + else + nil + end + end + + RE_LWS = /[\r\n\t ]+/n + RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n + RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n + RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n + + def content_type_parse # :nodoc: + v = @meta['content-type'] + # The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045. + if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v + type = $1.downcase + subtype = $2.downcase + parameters = [] + $3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval| + val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval + parameters << [att.downcase, val] + } + ["#{type}/#{subtype}", *parameters] + else + nil + end + end + + # returns "type/subtype" which is MIME Content-Type. + # It is downcased for canonicalization. + # Content-Type parameters are stripped. + def content_type + type, *parameters = content_type_parse + type || 'application/octet-stream' + end + + # returns a charset parameter in Content-Type field. + # It is downcased for canonicalization. + # + # If charset parameter is not given but a block is given, + # the block is called and its result is returned. + # It can be used to guess charset. + # + # If charset parameter and block is not given, + # nil is returned except text type in HTTP. + # In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1. + def charset + type, *parameters = content_type_parse + if pair = parameters.assoc('charset') + pair.last.downcase + elsif block_given? + yield + elsif type && %r{\Atext/} =~ type && + @base_uri && /\Ahttp\z/i =~ @base_uri.scheme + "iso-8859-1" # RFC2616 3.7.1 + else + nil + end + end + + # returns a list of encodings in Content-Encoding field + # as an Array of String. + # The encodings are downcased for canonicalization. + def content_encoding + v = @meta['content-encoding'] + if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v + v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase} + else + [] + end + end + end + + # Mixin for HTTP and FTP URIs. + module OpenRead + # OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP. + # + # OpenURI::OpenRead#open takes optional 3 arguments as: + # OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }] + # + # `mode', `perm' is same as Kernel#open. + # + # However, `mode' must be read mode because OpenURI::OpenRead#open doesn't + # support write mode (yet). + # Also `perm' is just ignored because it is meaningful only for file + # creation. + # + # `options' must be a hash. + # + # Each pairs which key is a string in the hash specify a extra header + # field for HTTP. + # I.e. it is ignored for FTP without HTTP proxy. + # + # The hash may include other options which key is a symbol: + # + # [:proxy] + # Synopsis: + # :proxy => "http://proxy.foo.com:8000/" + # :proxy => URI.parse("http://proxy.foo.com:8000/") + # :proxy => true + # :proxy => false + # :proxy => nil + # + # If :proxy option is specified, the value should be String, URI, + # boolean or nil. + # When String or URI is given, it is treated as proxy URI. + # When true is given or the option itself is not specified, + # environment variable `scheme_proxy' is examined. + # `scheme' is replaced by `http', `https' or `ftp'. + # When false or nil is given, the environment variables are ignored and + # connection will be made to a server directly. + # + # [:http_basic_authentication] + # Synopsis: + # :http_basic_authentication=>[user, password] + # + # If :http_basic_authentication is specified, + # the value should be an array which contains 2 strings: + # username and password. + # It is used for HTTP Basic authentication defined by RFC 2617. + # + # [:content_length_proc] + # Synopsis: + # :content_length_proc => lambda {|content_length| ... } + # + # If :content_length_proc option is specified, the option value procedure + # is called before actual transfer is started. + # It takes one argument which is expected content length in bytes. + # + # If two or more transfer is done by HTTP redirection, the procedure + # is called only one for a last transfer. + # + # When expected content length is unknown, the procedure is called with + # nil. + # It is happen when HTTP response has no Content-Length header. + # + # [:progress_proc] + # Synopsis: + # :progress_proc => lambda {|size| ...} + # + # If :progress_proc option is specified, the proc is called with one + # argument each time when `open' gets content fragment from network. + # The argument `size' `size' is a accumulated transfered size in bytes. + # + # If two or more transfer is done by HTTP redirection, the procedure + # is called only one for a last transfer. + # + # :progress_proc and :content_length_proc are intended to be used for + # progress bar. + # For example, it can be implemented as follows using Ruby/ProgressBar. + # + # pbar = nil + # open("http://...", + # :content_length_proc => lambda {|t| + # if t && 0 < t + # pbar = ProgressBar.new("...", t) + # pbar.file_transfer_mode + # end + # }, + # :progress_proc => lambda {|s| + # pbar.set s if pbar + # }) {|f| ... } + # + # OpenURI::OpenRead#open returns an IO like object if block is not given. + # Otherwise it yields the IO object and return the value of the block. + # The IO object is extended with OpenURI::Meta. + def open(*rest, &block) + OpenURI.open_uri(self, *rest, &block) + end + + # OpenURI::OpenRead#read([options]) reads a content referenced by self and + # returns the content as string. + # The string is extended with OpenURI::Meta. + # The argument `options' is same as OpenURI::OpenRead#open. + def read(options={}) + self.open(options) {|f| + str = f.read + Meta.init str, f + str + } + end + end +end + +module URI + class Generic + # returns a proxy URI. + # The proxy URI is obtained from environment variables such as http_proxy, + # ftp_proxy, no_proxy, etc. + # If there is no proper proxy, nil is returned. + # + # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) + # are examined too. + # + # But http_proxy and HTTP_PROXY is treated specially under CGI environment. + # It's because HTTP_PROXY may be set by Proxy: header. + # So HTTP_PROXY is not used. + # http_proxy is not used too if the variable is case insensitive. + # CGI_HTTP_PROXY can be used instead. + def find_proxy + name = self.scheme.downcase + '_proxy' + proxy_uri = nil + if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI? + # HTTP_PROXY conflicts with *_proxy for proxy settings and + # HTTP_* for header information in CGI. + # So it should be careful to use it. + pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k } + case pairs.length + when 0 # no proxy setting anyway. + proxy_uri = nil + when 1 + k, v = pairs.shift + if k == 'http_proxy' && ENV[k.upcase] == nil + # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = ENV[name] + else + proxy_uri = nil + end + else # http_proxy is safe to use because ENV is case sensitive. + proxy_uri = ENV[name] + end + if !proxy_uri + # Use CGI_HTTP_PROXY. cf. libwww-perl. + proxy_uri = ENV["CGI_#{name.upcase}"] + end + elsif name == 'http_proxy' + unless proxy_uri = ENV[name] + if proxy_uri = ENV[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.' + end + end + else + proxy_uri = ENV[name] || ENV[name.upcase] + end + + if proxy_uri && self.host + require 'socket' + begin + addr = IPSocket.getaddress(self.host) + proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr + rescue SocketError + end + end + + if proxy_uri + proxy_uri = URI.parse(proxy_uri) + name = 'no_proxy' + if no_proxy = ENV[name] || ENV[name.upcase] + no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port| + if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host && + (!port || self.port == port.to_i) + proxy_uri = nil + break + end + } + end + proxy_uri + else + nil + end + end + end + + class HTTP + def buffer_open(buf, proxy, options) # :nodoc: + OpenURI.open_http(buf, self, proxy, options) + end + + include OpenURI::OpenRead + end + + class FTP + def buffer_open(buf, proxy, options) # :nodoc: + if proxy + OpenURI.open_http(buf, self, proxy, options) + return + end + require 'net/ftp' + + directories = self.path.split(%r{/}, -1) + directories.shift if directories[0] == '' # strip a field before leading slash + directories.each {|d| + d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } + } + unless filename = directories.pop + raise ArgumentError, "no filename: #{self.inspect}" + end + directories.each {|d| + if /[\r\n]/ =~ d + raise ArgumentError, "invalid directory: #{d.inspect}" + end + } + if /[\r\n]/ =~ filename + raise ArgumentError, "invalid filename: #{filename.inspect}" + end + typecode = self.typecode + if typecode && /\A[aid]\z/ !~ typecode + raise ArgumentError, "invalid typecode: #{typecode.inspect}" + end + + # The access sequence is defined by RFC 1738 + ftp = Net::FTP.open(self.host) + # todo: extract user/passwd from .netrc. + user = 'anonymous' + passwd = nil + user, passwd = self.userinfo.split(/:/) if self.userinfo + ftp.login(user, passwd) + directories.each {|cwd| + ftp.voidcmd("CWD #{cwd}") + } + if typecode + # xxx: typecode D is not handled. + ftp.voidcmd("TYPE #{typecode.upcase}") + end + if options[:content_length_proc] + options[:content_length_proc].call(ftp.size(filename)) + end + ftp.retrbinary("RETR #{filename}", 4096) { |str| + buf << str + options[:progress_proc].call(buf.size) if options[:progress_proc] + } + ftp.close + buf.io.rewind + end + + include OpenURI::OpenRead + end +end diff --git a/lib/rsync_utils.rb b/lib/rsync_utils.rb new file mode 100644 index 0000000..8e8ee89 --- /dev/null +++ b/lib/rsync_utils.rb @@ -0,0 +1,62 @@ +require File.join(File.dirname(__FILE__), 'mock_file') +require File.join(File.dirname(__FILE__), 'shell_command') +require 'parsedate' +class RsyncUtils + + def initialize(timeout=4) + @timeout = timeout + @rsync = ShellCommand.new("rsync") + end + + def list(host) + raise "Invalid Path" unless check_host(host) + r = @rsync.run("--contimeout=#{@timeout} #{host}::") + raise "#{r.stderr}" unless r.status == 0 + r + end + + def show(host,path,recurs=false) + recursive = "--recursive" if recurs + raise "Invalid Path" unless check_host(host) + if path + raise "Invalid Path" unless check_path(path) + end + r = @rsync.run("--contimeout=#{@timeout} #{recursive} #{host}::#{path}") + raise "#{r.stderr}" unless r.status == 0 + files = [] + r.stdout.each do |line| + parts = line.split + case parts[0] + when /^[rwx-]{10}$/ + type = "File" + when /^d[rwx-]{9}$/ + type = "Directory" + else + type = "Module" + end + if type != "Module" + time = Time.local(*ParseDate::parsedate("#{parts[2]} #{parts[3]}")) + size = parts[1].to_i + name = parts[4] + else + time = Time.now + size = 0 + name = parts[0] + end + files << MockFile.new(name,type,time,size) + end + r.results = files + r + end + + private + + def check_host(host) + /^([a-zA-Z0-9\-\.]+)$/.match(host) + end + + def check_path(path) + /^([a-zA-Z0-9\-\.\/_]+)$/.match(path) + end + +end diff --git a/lib/shell_command.rb b/lib/shell_command.rb new file mode 100644 index 0000000..68dcaef --- /dev/null +++ b/lib/shell_command.rb @@ -0,0 +1,40 @@ +require 'rubygems' +require 'open4' +class ShellCommand + + attr_reader :pid, :stdout, :stderr, :status + attr_accessor :command, :results + + def initialize(cmd) + @command = find_command(cmd) + end + + def run(parameters) + status = Open4::popen4("#{@command} #{parameters}") do + |pid, stdin, stdout, stderr| + @pid = pid + @stdout = stdout.read.strip + @stderr = stderr.read.strip + end + @status = status.exitstatus + return self + end + + private + + def find_command(cmd) + command = nil + safe_path = File.join(File.dirname(__FILE__), "..", 'libexec') + if(test(?x, "#{safe_path}/#{cmd}")) + command = "#{safe_path}/#{cmd}" + end + if command.nil? + raise( + StandardError, + "Cannot find #{cmd}, specify full path or symlink in ./libexec" + ) + end + command + end + +end diff --git a/lib/snmp_utils.rb b/lib/snmp_utils.rb new file mode 100644 index 0000000..1fe155d --- /dev/null +++ b/lib/snmp_utils.rb @@ -0,0 +1,26 @@ +require File.join(File.dirname(__FILE__), 'shell_command') +require 'yaml' +class SnmpUtils + + @@config = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'etc', 'snmp.yml')) + + def initialize(host) + if @@config['host'] + community = @@config['host']['community'] + else + community = @@config['default']['community'] + end + @host = host + @snmpwalk = ShellCommand.new("snmpwalk") + @snmpget = ShellCommand.new("snmpget") + @parameters = "-v2c -On -OQ -OU -c #{community}" + end + + def walk(oid) + r = @snmpwalk.run("#{@parameters} #{@host} #{oid}") + end + + def get(oid) + r = @snmpget.run("#{@parameters} #{@host} #{oid}") + end +end diff --git a/lib/system_cpu.rb b/lib/system_cpu.rb new file mode 100644 index 0000000..0f24756 --- /dev/null +++ b/lib/system_cpu.rb @@ -0,0 +1,38 @@ +require File.join(File.dirname(__FILE__), 'snmp_utils') +class SystemCpu + + OID_BASE = ".1.3.6.1.2.1.25.3.3.1.2" + + attr_reader :id, :used_percent + + def initialize(id,used) + @id,@used_percent = id,used + end + + def self.find_by_host(host) + snmp = SnmpUtils.new(host) + r = snmp.walk(OID_BASE) + raise "#{r.stderr}" unless r.status == 0 + cpus = [] + r.stdout.each do |line| + parts = line.split('=') + parts.collect! {|part| part.delete('"').strip! } + p = SystemCpu.new(parts[0].split('.').last.to_i, parts[1].to_i) + cpus << p + end + cpus + end + + def to_s + "id: #{@id}\n used_percent: #{@used_percent}\n\n" + end + + def to_xml + xml = Builder::XmlMarkup.new(:indent=>2) + xml.cpu do + xml.id id + xml.used_percent @used_percent + end + end + +end diff --git a/lib/system_load.rb b/lib/system_load.rb new file mode 100644 index 0000000..2ecdf9d --- /dev/null +++ b/lib/system_load.rb @@ -0,0 +1,55 @@ +require File.join(File.dirname(__FILE__), 'snmp_utils') +class SystemLoad + + OID_BASE = ".1.3.6.1.4.1.2021.10.1.3" + OID_1 = OID_BASE + ".1" + OID_5 = OID_BASE + ".2" + OID_15 = OID_BASE + ".3" + + attr_reader :load + + def initialize(load) + @load = load + raise(StandardError, "Invalid argument") unless @load.is_a?(Hash) + end + + def one + load["1"] + end + + def five + load["5"] + end + + def fifteen + load["15"] + end + + def self.find_by_host(host) + snmp = SnmpUtils.new(host) + r = snmp.walk(OID_BASE) + raise "#{r.stderr}" unless r.status == 0 + load = {} + r.stdout.each do |line| + parts = line.split('=') + case parts[0].strip + when OID_1 + load["1"] = parts[1].to_f + when OID_5 + load["5"] = parts[1].to_f + when OID_15 + load["15"] = parts[1].to_f + end + end + SystemLoad.new(load) + end + + def to_s + "#{one}, #{five}, #{fifteen}" + end + + def to_xml + raise NotImplementedError + end + +end diff --git a/lib/system_memory_usage.rb b/lib/system_memory_usage.rb new file mode 100644 index 0000000..9aab975 --- /dev/null +++ b/lib/system_memory_usage.rb @@ -0,0 +1,51 @@ +require File.join(File.dirname(__FILE__), 'snmp_utils') +class SystemMemoryUsage + + # hrSWRunPerfMem + OID_BASE = ".1.3.6.1.2.1.25.5.1.1.2" + + attr_reader :pid + attr_accessor :memory, :name + + def initialize(pid) + @pid = pid + end + + def memory_mb + memory / 1024 + end + + def to_s + <2) + xml.process do + xml.pid = pid + xml.memory(memory, :units => "K") + end + end + + def find_by_pid(collection) + collection.detect {|c| c.pid == pid } + end + + def self.find_by_host(host) + snmp = SnmpUtils.new(host) + r = snmp.walk(OID_BASE) + raise "#{r.stderr}" unless r.status == 0 + processes = [] + r.stdout.each do |line| + parts = line.split('=') + parts.collect! {|part| part.delete('"').strip! } + p = SystemMemoryUsage.new(parts[0].split('.').last.to_i) + p.memory = parts[1].to_i + processes << p + end + processes + end + +end diff --git a/lib/system_process.rb b/lib/system_process.rb new file mode 100644 index 0000000..7acda53 --- /dev/null +++ b/lib/system_process.rb @@ -0,0 +1,82 @@ +require File.join(File.dirname(__FILE__), 'snmp_utils') +class SystemProcess + + # hrSWRun + OID_BASE = ".1.3.6.1.2.1.25.4.2.1" + OID_INDEX = OID_BASE + ".1" + OID_NAME = OID_BASE + ".2" + OID_ID = OID_BASE + ".3" + OID_PATH = OID_BASE + ".4" + OID_PARAMETERS = OID_BASE + ".5" + OID_TYPE = OID_BASE + ".6" + OID_STATUS = OID_BASE + ".7" + + attr_reader :pid + attr_accessor :name, :path, :parameters, :type, :status + + def initialize(pid) + @pid = pid + end + + def to_s + <2) + xml.process do + xml.pid = pid + xml.name = name + end + end + + def self.find_all_by_host(host) + snmp = SnmpUtils.new(host) + r = snmp.walk(OID_BASE) + raise "#{r.stderr}" unless r.status == 0 + matches = Hash.new + r.stdout.each do |line| + parts = line.split('=') + parts.collect! {|part| part.delete('"').strip! } + case parts[0] + when /^#{Regexp.escape(OID_INDEX)}\.(\d+)$/ + matches[$1.to_i] = SystemProcess.new($1.to_i) + when /^#{Regexp.escape(OID_NAME)}\.(\d+)$/ + matches[$1.to_i].name = parts[1] + when /^#{Regexp.escape(OID_PATH)}\.(\d+)$/ + matches[$1.to_i].path = parts[1] + when /^#{Regexp.escape(OID_PARAMETERS)}\.(\d+)$/ + matches[$1.to_i].parameters = parts[1] + when /^#{Regexp.escape(OID_TYPE)}\.(\d+)$/ + matches[$1.to_i].type = parts[1] + when /^#{Regexp.escape(OID_STATUS)}\.(\d+)$/ + matches[$1.to_i].status = parts[1] + end + end + processes = Array.new + matches.keys.each {|k| processes << matches[k]} + processes + end + + def self.find_by_host(host) + snmp = SnmpUtils.new(host) + r = snmp.walk(OID_PATH) + raise "#{r.stderr}" unless r.status == 0 + processes = [] + r.stdout.each do |line| + parts = line.split('=') + parts.collect! {|part| part.delete('"').strip! } + p = SystemProcess.new(parts[0].split('.').last.to_i) + p.name = parts[1] + processes << p + end + processes + end + + def self.find_by_pid(collection) + collection.detect {|c| c.pid == pid } + end + +end diff --git a/lib/system_storage.rb b/lib/system_storage.rb new file mode 100644 index 0000000..907c627 --- /dev/null +++ b/lib/system_storage.rb @@ -0,0 +1,172 @@ +require File.join(File.dirname(__FILE__), 'snmp_utils') +class SystemStorage + + # hrStorage + OID_BASE = ".1.3.6.1.2.1.25.2.3.1" + OID_INDEX = OID_BASE + ".1" + OID_TYPE = OID_BASE + ".2" + OID_DESCRIPTION = OID_BASE + ".3" + OID_UNITS = OID_BASE + ".4" + OID_SIZE = OID_BASE + ".5" + OID_USED = OID_BASE + ".6" + OID_FAILURES = OID_BASE + ".7" + + # Storage Types + OID_TYPE_BASE = ".1.3.6.1.2.1.25.2.1" + OID_TYPE_OTHER = OID_TYPE_BASE + ".1" + OID_TYPE_RAM = OID_TYPE_BASE + ".2" + OID_TYPE_VIRTUAL = OID_TYPE_BASE + ".3" + OID_TYPE_FIXED = OID_TYPE_BASE + ".4" + OID_TYPE_REMOVABLE = OID_TYPE_BASE + ".5" + OID_TYPE_FLOPPY = OID_TYPE_BASE + ".6" + OID_TYPE_COMPACT = OID_TYPE_BASE + ".7" + OID_TYPE_RAM_DISK = OID_TYPE_BASE + ".8" + OID_TYPE_FLASH = OID_TYPE_BASE + ".9" + OID_TYPE_NETWORK = OID_TYPE_BASE + ".10" + + + attr_reader :id, :type + attr_accessor :label, :block_size, :total_blocks, :used_blocks, :failures + + def initialize(id) + @id = id + end + + def size + @size ||= (block_size * total_blocks) + end + + def used + @used ||= (block_size * used_blocks) + end + + def free + @free ||= (size - used) + end + + def size_mb + @size_mb ||= (size / 1024 / 1024) + end + + def used_mb + @used_mb ||= (used / 1024 / 1024) + end + + def free_mb + @free_mb ||= (size_mb - used_mb) + end + + def used_percent + @used_percent ||= get_used_percent + end + + def free_percent + @free_percent ||= get_free_percent + end + + def type=(type) + @type = find_type(type) + end + + def block_size + @block_size ||= 0 + end + + def total_blocks + @total_blocks ||= 0 + end + + def used_blocks + @used_blocks ||= 0 + end + + def to_s + < /tmp/plugin_output.txt +