Skip to content
This repository has been archived by the owner on May 7, 2018. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Missing files?
  • Loading branch information
成実Musee committed Jun 27, 2012
1 parent 711d745 commit 4025a9c
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 0 deletions.
40 changes: 40 additions & 0 deletions bin/z2monitor
@@ -0,0 +1,40 @@
#!/usr/bin/ruby

require 'optparse'
require 'z2monitor'

opts = {}
OptionParser.new do |o|
o.banner = "usage: z2monitor [options]"
o.on('--ack MATCH', '-a', "Acknowledge current events that match a pattern MATCH. No wildcards.") { |a| opts[:ack] = a.tr('^ A-Za-z0-9[]{}()|,-.', '') }
o.on('--disable-maintenance', '-m', "Filter out servers marked as being in maintenance.") { |m| opts[:maint] = 1 }
o.on('--minimum-severity PRIORITY', '-M', "Show events with a priority greater than M. Accepted values are 0 to 5. Default is 2.") { |ms| opts[:min_severity] = ms.tr('^0-5', '') }
o.on('--priority-list LIST', '-l', "Comma-delimited list of what priority events to show.") { |l| opts[:priority_list] = l.tr('^,0-5', '') }
o.on('--hide-acknowledged-alerts', '-H', "Don't show events that have already been acknowledged.") { |h| opts[:hideack] = 1 }
o.on('--print-once', '-1', "Only check Zabbix once and print out all alerts.") { |p| opts[:once] = 1 }
o.on('-h', 'Show this help') { puts '',o,''; exit }
o.parse!
end

monitor = Zabbix::Monitor.new()
monitor.hide_maintenance = opts[:maint] unless opts[:maint].nil?
monitor.hide_acknowledged_alerts = opts[:hideack] unless opts[:hideack].nil?
monitor.min_severity = opts[:min_severity] unless opts[:min_severity].nil? and opts[:min_severity] != ''
monitor.priority_list = opts[:priority_list] unless opts[:priority_list].nil?

if opts[:ack]
monitor.acknowledge(opts[:ack])
elsif opts[:once]
monitor.get_dashboard('full')
else
begin
system "stty -echo"
Signal.trap("SIGINT") { abort }
while true
monitor.get_dashboard()
0.upto(20) { sleep 0.5 }
end
ensure
system "stty echo"
end
end
170 changes: 170 additions & 0 deletions lib/z2monitor.rb
@@ -0,0 +1,170 @@
#!/usr/bin/ruby

require 'colored'

require 'z2monitor/api'
require 'z2monitor/misc'

module Zabbix
class Monitor
attr_accessor :api, :hide_maintenance, :hide_acknowledged_alerts, :min_severity, :priority_list

class EmptyFileError < StandardError
attr_reader :message
def initialize(reason, file)
@message = reason
puts "[INFO] Deleting #{file}"
File.delete(file)
end
end
def initialize()
@hide_maintenance = 0
@hide_acknowledged_alerts = 0
@min_severity = '2'
@priority_list = ''
uri = self.check_uri()
@api = Zabbix::API.new(uri)
@api.token = self.check_login
end
def check_uri()
uri_path = File.expand_path("~/.zmonitor-server")
if File.exists?(uri_path)
uri = File.open(uri_path).read()
else
puts "Where is your Zabbix located? (please include https/http - for example, https://localhost)"
uri = "#{STDIN.gets.chomp()}/api_jsonrpc.php"
f = File.new(uri_path, "w+")
f.write(uri)
f.close
end
#if uri !=~ /^https?:\/\/.*\/api_jsonrpc\.php/
#puts "The URI we're using is invalid, sir. Resetting..."
#check_uri()
#end
#puts "Okay, using #{uri}."
raise EmptyFileError.new('URI is empty for some reason', uri_path) if uri == '' || uri.nil?
return uri
end
def check_login()
token_path = File.expand_path("~/.zmonitor-token")
if File.exists?(token_path)
token = File.open(token_path).read()
else
print "Please enter your Zabbix username: "
user = STDIN.gets.chomp()
print "Please enter your Zabbix password: "
begin
system "stty -echo"
password = gets.chomp
ensure
system "stty echo"
puts
end
token = @api.user.login(user, password).chomp
f = File.new(token_path, "w+")
f.write(token)
f.close
end
raise EmptyFileError.new("Token is empty!", token_path) if token == '' || token.nil?
return token
end
def get_events()
current_time = Time.now.to_i # to be used in getting event durations, but it really depends on the master
triggers = unacked_triggers = @api.trigger.get_active(@min_severity, @hide_maintenance, @hide_acknowledged_alerts, @priority_list) # Call the API for a list of active triggers
unacked_triggers = @api.trigger.get_active(@min_severity, @hide_maintenance, 1, @priority_list) if @hide_acknowledged_alerts == 0 # Call it again to get just those that are unacknowledged
current_events = []
triggers.each do |t|
next if t['hosts'][0]['status'] == '1' or t['items'][0]['status'] == '1' # skip disabled items/hosts that the api call returns
current_events << {
:id => t['triggerid'].to_i,
:time => t['lastchange'].to_i,
:fuzzytime => fuzz(current_time - t['lastchange'].to_i),
:severity => t['priority'].to_i,
:hostname => t['host'],
:description => t['description'].gsub(/ (on(| server) |to |)#{t['host']}/, '')#,
}
end
current_events.each do |e|
s = unacked_triggers.select{ |t| t['triggerid'] == "#{e[:id]}" }
e[:acknowledged] = s[0] ? 0 : 1
end
# Sort the events decreasing by severity, and then descending by duration (smaller timestamps at top)
return current_events.sort_by { |t| [ -t[:severity], t[:time] ] }
end
def get_dashboard(format = '')
max_lines = `tput lines`.to_i - 1
cols = `tput cols`.to_i
eventlist = self.get_events() #TODO: get_events(max_lines)
pretty_output = []
pretty_output << ["Last updated: %8s%#{cols-40}sZ2monitor Dashboard".cyan_on_blue % [Time.now.strftime('%T'),'']] if format != 'full'
if eventlist.length != 0
max_hostlen = eventlist.each.max { |a,b| a[:hostname].length <=> b[:hostname].length }[:hostname].length
max_desclen = eventlist.each.max { |a,b| a[:description].length <=> b[:description].length }[:description].length
eventlist.each do |e|
break if pretty_output.length == max_lines and format != 'full'
ack = "N".red
ack = "Y".green if e[:acknowledged] == 1
pretty_output << "%s " % e[:fuzzytime] + "%-#{max_hostlen}s " % e[:hostname] +
"%-#{max_desclen}s".color_by_severity(e[:severity]) % e[:description] + " %s" % ack
end
else
pretty_output << ['',
'The API calls returned 0 results. Either your servers are very happy, or Z2Monitor is not working correctly.',
'', "Please check your dashboard at #{@api.server.to_s.gsub(/\/api_jsonrpc.php/, '')} to verify activity.", '',
'Z2Monitor will continue to refresh every ten seconds unless you interrupt it.']
end
print "\e[H\e[2J" if format != 'full' # clear terminal screen
puts pretty_output
end
def acknowledge(pattern = '')
puts 'Retrieving list of active unacknowledged triggers that match: '.bold.blue + '%s'.green % pattern, ''
filtered = []
eventlist = self.get_events()
eventlist.each do |e|
if e[:hostname] =~ /#{pattern}/ or e[:description] =~ /#{pattern}/
event = @api.event.get_last_by_trigger(e[:id])
e[:eventid] = event['eventid'].to_i
e[:acknowledged] = event['acknowledged'].to_i
filtered << e if e[:acknowledged] == 0
end
end
abort("No alerts found, so aborting".yellow) if filtered.length == 0
filtered.each.with_index do |a,i|
message = '%s - %s (%s)'.color_by_severity(a[:severity]) % [ a[:fuzzytime], a[:description], a[:hostname] ]
puts "%4d >".bold % (i+1) + message
end

puts '', ' Selection - enter "all", or a set of numbers listed above separated by spaces.'
print ' Sel > '.bold
input = STDIN.gets.chomp()

no_ack_msg = "Not acknowledging anything."
raise StandardError.new("No input. #{no_ack_msg}".green) if input == ''
to_ack = (1..filtered.length).to_a if input =~ /^\s*all\s*$/ # only string we'll accept
raise StandardError.new("Invalid input. #{no_ack_msg}".red) if to_ack.nil? and (input =~ /^([0-9 ]+)$/).nil?
to_ack = input.split.map(&:to_i).sort if to_ack.nil? # Split our input into a sorted array of integers
# Let's first check if a value greater than possible was given, to help prevent typos acknowledging the wrong thing
to_ack.each { |i| raise StandardError.new("You entered a value greater than %d! Please double check. #{no_ack_msg}".yellow % filtered.length) if i > filtered.length }

puts '', ' Message - enter an acknowledgement message below, or leave blank for the default.'
print ' Msg > '.bold
message = STDIN.gets.chomp()
puts

# Finally! Acknowledge EVERYTHING
to_ack.each do |a|
puts 'Acknowledging: '.green + '%s (%s)' % [ filtered[a-1][:description], filtered[a-1][:hostname] ]
if message == ''
@api.whoami = @api.user.get_fullname()
@api.event.acknowledge(filtered[a-1][:eventid])
else
@api.event.acknowledge(filtered[a-1][:eventid], message)
end
end
end
# Save a time offset between the local computer and the Zabbix master
def calibrate()
#
end
end
end
98 changes: 98 additions & 0 deletions lib/z2monitor/api.rb
@@ -0,0 +1,98 @@
#!/usr/bin/ruby

require 'json'
require 'net/http'
require 'net/https'

abort("Could not load API libraries. Did you install a JSON library? (json / json_pure / json-jruby)") unless Object.const_defined?(:JSON)

# create the module/class stub so we can require the API class files properly
module Zabbix
class API
end
end

# load up the different API classes and methods
require 'z2monitor/api/event'
require 'z2monitor/api/trigger'
require 'z2monitor/api/user'

module Zabbix
class API
attr_accessor :server, :verbose, :token, :whoami

attr_accessor :event, :trigger, :user # API classes

def initialize( server = "http://localhost", verbose = false)
# Parse the URL beforehand
@server = URI.parse(server)
@verbose = verbose

# set up API class methods
@user = Zabbix::User.new(self)
@event = Zabbix::Event.new(self)
@trigger = Zabbix::Trigger.new(self)
end

# More specific error names, may add extra handling procedures later
class ResponseCodeError < StandardError
end
class ResponseError < StandardError
end
class NotAuthorisedError < StandardError
end

def call_api(message)
# Finish preparing the JSON call
message['id'] = rand 65536 if message['id'].nil?
message['jsonrpc'] = '2.0'
# Check if we have authorization token if we're not logging in
if @token.nil? && message['method'] != 'user.login'
puts "[ERROR] Authorisation Token not initialised. message => #{message}"
raise NotAuthorisedError.new()
else
message['auth'] = @token if message['method'] != 'user.login'
end

json_message = JSON.generate(message) # Create a JSON string

# Open TCP connection to Zabbix master
connection = Net::HTTP.new(@server.host, @server.port)
connection.read_timeout = 300
# Check to see if we're connecting via SSL
if @server.scheme == 'https' then
connection.use_ssl = true
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
end

# Prepare POST request for sending
request = Net::HTTP::Post.new(@server.request_uri)
request.add_field('Content-Type', 'application/json-rpc')
request.body = json_message

# Send request
begin
puts "[INFO] Attempting to send request => #{request}, request body => #{request.body}" if @verbose
response = connection.request(request)
rescue ::SocketError => e
puts "[ERROR] Could not complete request: SocketError => #{e.message}" if @verbose
raise SocketError.new(e.message)
rescue Timeout::Error => e
puts "[ERROR] Timed out from Zabbix master. Is it being funky? => #{e.message}"
exit
end

puts "[INFO] Received response: #{response}" if @verbose
raise ResponseCodeError.new("[ERROR] Did not receive 200 OK, but HTTP code #{response.code}") if response.code != "200"

# Check for an error, and return the parsed result if everything's fine
parsed_response = JSON.parse(response.body)
if error = parsed_response['error']
raise ResponseError.new("[ERROR] Received error response: code => #{error['code'].to_s}; message => #{error['message']}; data => #{error['data']}")
end

return parsed_response['result']
end
end
end

39 changes: 39 additions & 0 deletions lib/z2monitor/api/event.rb
@@ -0,0 +1,39 @@
# api.event functions

module Zabbix
class Event < API
attr_accessor :parent
def initialize(parent)
@parent = parent
@verbose = @parent.verbose
end
def call_api(message)
return @parent.call_api(message)
end
# General event.get
def get( options = {} )
request = { 'method' => 'event.get', 'params' => options }
return call_api(request)
end
# Get the most recent event's information for a particular trigger
def get_last_by_trigger( triggerid = '' )
request = {
'method' => 'event.get',
'params' =>
{
'triggerids' => [triggerid.to_s],
'sortfield' => 'clock',
'sortorder' => 'DESC',
'limit' => '1',
'output' => 'extend'
}
}
return call_api(request)[0]
end
# Mark an event acknowledged and leave a message
def acknowledge( events = [], message = "#{@parent.whoami} is working on this." )
request = { 'method' => 'event.acknowledge', 'params' => { 'eventids' => events, 'message' => message } }
call_api(request)
end
end
end
40 changes: 40 additions & 0 deletions lib/z2monitor/api/user.rb
@@ -0,0 +1,40 @@
# api.user functions

module Zabbix
class User < API
attr_accessor :parent
def initialize(parent)
@parent = parent
@verbose = @parent.verbose
end
def call_api(message)
return @parent.call_api(message)
end
# General user.get
def get( options = {} )
request = { 'method' => 'user.get', 'params' => options }
return call_api(request)
end
# Get first and last name of currently logged in user
def get_fullname()
request = { 'method' => 'user.get', 'output' => 'extend' }
whoami = self.get({ 'output' => 'extend' })
return whoami[0]["name"] + " " + whoami[0]["surname"]
end
# Perform a login procedure
def login(user, password)
request = { 'method' => 'user.login', 'params' => { 'user' => user, 'password' => password, }, 'id' => 1 }
puts "[INFO] Logging in..." if @verbose
result = call_api(request)
puts "[INFO] Successfully logged in as #{user}! result => #{result}" if @verbose
return result
end
# Perform a logout
def logout()
request = { 'method' => 'user.logout' }
puts "[INFO] Logging out..." if @verbose
call_api(request)
puts "[INFO] Successfully logged out." if @verbose
end
end
end

0 comments on commit 4025a9c

Please sign in to comment.