#!/opt/ruby/bin/ruby ## ## Author: Andy Walker <andy@fbsdata.com> ## Copyright: Copyright (c) 2012 FBS Datasystems ## License: GNU General Public License ## Websites: https://github.com/walkeran/nagios-check_dellwarranty ## http://exchange.nagios.org/directory/Plugins/Hardware/Server-Hardware/Dell/check_dellwarranty/details ## https://www.monitoringexchange.org/inventory/Check-Plugins/Hardware/Server-%2528Manufacturer%2529/check_dellwarranty ## ## This program is free software: you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation, either version 3 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program. If not, see <http://www.gnu.org/licenses/>. ## begin require 'date' require 'optparse' require 'soap/wsdlDriver' rescue Exception => e puts "You need the date, optparse, and soap libraries installed." puts e.message exit 2 end WSDL_URL = 'http://xserv.dell.com/services/assetservice.asmx?WSDL' GUID = '11111111-1111-1111-1111-111111111111' App = 'check_dellwarranty.rb' PLUGIN_VERSION = '0.5' Errlevels = { 0 => "OK", 1 => "WARNING", 2 => "CRITICAL", 3 => "UNKNOWN" } options = {} optparse = OptionParser.new do|opts| opts.banner = "check_dellwarranty https://github.com/walkeran/nagios-check_dellwarranty\n" + "Author: Andy Walker <andy@fbsdata.com>\nVersion: #{PLUGIN_VERSION}\n\n" + "Usage: #{App} -H <hostname> | -s <servicetag> [options]" options[:hostname] = "" opts.on( '-H', '--hostname <hostname>', 'Hostname to get warranty status for. Uses SNMP' ) do |hostname| options[:hostname] = hostname end options[:serial] = "" opts.on( '-s', '--servicetag <servicetag>', 'ServiceTag ID to check' ) do |serial| options[:serial] = serial end options[:snmp_comm] = 'public' opts.on( '-C', '--community <community>', 'SNMP Community to use when polling for service tag (Default: public)') do |comm| options[:snmp_comm] = comm end options[:snmp_version] = :SNMPv2c opts.on( '-v', '--snmpver <snmpver>', 'SNMP Version to use when polling for service tag (Default: 2c)') do |ver| case ver when '1' options[:snmp_ver] = :SNMPv1 when '2c' options[:snmp_var] = :SNMPv2c else puts "That SNMP version is not supported. Use 1 or 2c only" exit 2 end end options[:warn_days] = 90 opts.on( '-w', '--warning <days>', 'Warning threshold for number of days remaining on contract (Default: 90)' ) do |w| options[:warn_days] = w.to_i end options[:crit_days] = 30 opts.on( '-c', '--critical <days>', 'Critical threshold for number of days remaining on contract (Default: 30)' ) do |c| options[:crit_days] = c.to_i end options[:distant] = false opts.on( '-D', '--distant', 'Consider only the contract expiring in the most distant future' ) do |d| options[:distant] = d end options[:link] = false opts.on( '-l', '--link', 'Include an HTML link to Dell\'s warranty page for this server' ) do |l| options[:link] = l end options[:verbose] = false opts.on( '-v', '--verbose', 'Enable verbose output' ) do |v| options[:verbose] = v end options[:debug] = false opts.on( '-d', '--debugging', 'Enable debugging output (Implies -v)' ) do |d| options[:debug] = d end opts.on_tail( '-h', '--help', 'Display this screen' ) do puts opts exit 2 end end begin optparse.parse! rescue StandardError => e puts "Error parsing command line arguments." puts e.message puts optparse exit 2 end class ServiceLevel attr_accessor :serviceLevelDescription, :serviceLevelCode def endDate @endDate end def endDate=(endDate) if endDate.is_a?(DateTime) @endDate = endDate elsif endDate.is_a?(String) @endDate = DateTime.parse(endDate) else puts "endDate doesn't accept " + endDate.class.to_s + " types!" exit 2 end end def <=>(other) self.endDate <=> other.endDate end def to_s @serviceLevelDescription + ", " + @serviceLevelCode + ", " + @endDate.strftime('%Y/%m/%d') end end class DellEntitlements def initialize @entitlements = Array.new @servicelevels = Hash.new end def servicelevels @servicelevels end def entitlements @entitlements end def add(ent) @entitlements.push ent # Gloss over Expired entitlements. Should we be alerting on # these? Maybe... maybe not... will have to wait for input if ent.entitlementType == "Expired" return end slkey = '' if ent.serviceLevelCode == nil # This is a somewhat special case, where Dell doesn't supply service # level codes or descriptions. We should keep track of all of these # service levels separately, so we'll calculate a new key for it # TODO: We should probably check for key collisions at some point. I # don't foresee this as beinga problem, but you never know! slkey = @servicelevels.length.to_s elsif @servicelevels[ent.serviceLevelCode] != nil # In this case, Dell has given us a service level code, and our # hash is already tracking this type. Let's just extend the endDate # if it goes beyond the one that's already recorded @servicelevels[ent.serviceLevelCode].endDate = [ @servicelevels[ent.serviceLevelCode].endDate, ent.endDate ].max # And then bail out... return else # Otherwise, we have a decent service level code that we can use as a key slkey = ent.serviceLevelCode end # If we get this far, we should add the new service level using the key we've come up with servicelevel = ServiceLevel.new servicelevel.endDate = ent.endDate servicelevel.serviceLevelDescription = ent.serviceLevelDescription if ent.serviceLevelDescription servicelevel.serviceLevelCode = ent.serviceLevelCode if ent.serviceLevelDescription @servicelevels[slkey] = servicelevel end end class DellEntitlement < ServiceLevel attr_accessor :entitlementType, :provider def initialize(args) @entitlementType = args[:type] @serviceLevelDescription = args[:desc] if args[:desc] @provider = args[:prov] if args[:prov] @serviceLevelCode = args[:code] if args[:code] self.startDate = args[:startDate] self.endDate = args[:endDate] end def startDate @startDate end def startDate=(startDate) if startDate.is_a?(DateTime) @startDate = startDate elsif startDate.is_a?(String) @startDate = DateTime.parse(startDate) else puts "startDate doesn't accept " + startDate.class.to_s + " types!" exit 2 end end end def suppress_warning back = $VERBOSE $VERBOSE = nil begin yield ensure $VERBOSE = back end end def get_snmp_serial ( args ) try_count = 0 begin try_count += 1 require 'snmp' rescue LoadError if try_count == 1 require 'rubygems' retry else raise end end serial = '' SNMP::Manager.open(:host => args[:hostname], :community => args[:community], :version => args[:version]) do |manager| val = manager.get_value('1.3.6.1.4.1.674.10892.1.300.10.1.11.1') serial = val.split[0] end return serial rescue Exception => e puts "Failed to get serial via SNMP: #{e.class}: #{e}" exit 2 end def get_dell_warranty(serial) ents = DellEntitlements.new driver = suppress_warning { SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver } result = driver.GetAssetInformation(:guid => GUID, :applicationName => App, :serviceTags => serial) aResult = Array resultType = result.getAssetInformationResult.asset.entitlements.entitlementData.class.to_s if resultType == 'Array' aResult = result.getAssetInformationResult.asset.entitlements.entitlementData elsif resultType == 'SOAP::Mapping::Object' aResult = [ result.getAssetInformationResult.asset.entitlements.entitlementData ] else puts "Returned entitlementData from Dell is a " + resultType + ", and I don't know how to deal with that!" exit 2 end aResult.each do | ent | entargs = Hash.new entargs[:type] = ent.entitlementType entargs[:startDate] = ent.startDate entargs[:endDate] = ent.endDate entargs[:prov] = ent.provider if defined? ent.provider entargs[:desc] = ent.serviceLevelDescription if defined? ent.serviceLevelDescription entargs[:code] = ent.serviceLevelCode if defined? ent.serviceLevelCode ents.add DellEntitlement.new(entargs) end ents end def expire_message(errlevel, daysleft, desc) if desc "\n#{Errlevels[errlevel]}: '#{desc}' support ends in #{daysleft} days" else "\n#{Errlevels[errlevel]}: A support contract ends in #{daysleft} days" end end servicelevels = Hash.new serial = '' now = DateTime.now errlevel = 0 count = 0 expiring = 0 nextexpire = nil outmsg = '' if options[:debug] options[:verbose] = true end if options[:crit_days] <= 0 puts "ERROR: -w and -c must be positive integers" exit 2 end if options[:crit_days] > options[:warn_days] puts "ERROR: -w cannot be less than -c" exit 2 end if options[:hostname].length > 0 puts "Hostname: #{options[:hostname]}" if options[:debug] serial = get_snmp_serial( :hostname => options[:hostname], :community => options[:snmp_comm], :version => options[:snmp_ver] ) elsif options[:serial].length > 0 serial = options[:serial] else puts "ERROR: Must supply either a hostname or servicetag!" puts optparse exit 2 end if options[:link] outmsg = " <a target=\"_blank\" href=\"http://www.dell.com/support/troubleshooting/Index?t=warranty&servicetag=#{serial}\">#{serial}</a>" end puts "Serial: #{serial}" if options[:debug] servicelevels = get_dell_warranty(serial).servicelevels.values.sort servicelevels.each do |sl| endDate = sl.endDate desc = sl.serviceLevelDescription daysleft = (endDate - now).round count += 1 if daysleft >= 0 nextexpire = (nextexpire == nil) ? daysleft : [nextexpire,daysleft].min end if daysleft <= options[:crit_days] outmsg += expire_message(2, daysleft, desc) if options[:verbose] expiring += 1 errlevel = [ errlevel, 2 ].max elsif daysleft <= options[:warn_days] outmsg += expire_message(1, daysleft, desc) if options[:verbose] expiring += 1 errlevel = [ errlevel, 1 ].max else outmsg += expire_message(0, daysleft, desc) if options[:verbose] end end if options[:distant] sl = servicelevels.last endDate = sl.endDate desc = sl.serviceLevelDescription daysleft = (endDate - now).round if daysleft <= options[:crit_days] errlevel = 2 elsif daysleft <= options[:warn_days] errlevel = 1 else errlevel = 0 end puts "#{Errlevels[errlevel]}: Most distant expiration is in #{daysleft} days#{outmsg}" else puts "#{Errlevels[errlevel]}: #{expiring} of #{count} service contracts are expiring (Next: #{nextexpire} days)#{outmsg}" end exit errlevel