This repository has been archived by the owner on May 7, 2018. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
418 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.