CheckMysqlReplicationStatus CRITICAL: undefined method `encoding' for nil:NilClass - ruby

sensu CheckMysqlReplication.rb on db servers returns undefined method `encoding' for nil:NilClass when executed in mysql 8.0.13-4 with latin1 as the default character set name. Prior to this error, the CheckMysqlReplication.rb worked perfectly ok on mysql 5.0 with UTF-8 as the default charset.
I have gleaned the code but I am unable to pinpoint where the problem might be from.
here's the check below:
require 'sensu-plugin/check/cli'
require 'mysql'
require 'inifile'
class CheckMysqlReplicationStatus < Sensu::Plugin::Check::CLI
option :host,
short: '-h',
long: '--host=VALUE',
description: 'Database host'
option :port,
short: '-P',
long: '--port=VALUE',
description: 'Database port',
default: 3306,
# #YELLOW
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
option :socket,
short: '-s SOCKET',
long: '--socket SOCKET',
description: 'Socket to use'
option :user,
short: '-u',
long: '--username=VALUE',
description: 'Database username'
option :pass,
short: '-p',
long: '--password=VALUE',
description: 'Database password'
option :master_connection,
short: '-m',
long: '--master-connection=VALUE',
description: 'Replication master connection name'
option :ini,
short: '-i',
long: '--ini VALUE',
description: 'My.cnf ini file'
option :ini_section,
description: 'Section in my.cnf ini file',
long: '--ini-section VALUE',
default: 'client'
option :warn,
short: '-w',
long: '--warning=VALUE',
description: 'Warning threshold for replication lag',
default: 900,
# #YELLOW
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
option :crit,
short: '-c',
long: '--critical=VALUE',
description: 'Critical threshold for replication lag',
default: 1800,
# #YELLOW
proc: lambda { |s| s.to_i } # rubocop:disable Lambda
def detect_replication_status?(row)
%w[
Slave_IO_State
Slave_IO_Running
Slave_SQL_Running
Last_IO_Error
Last_SQL_Error
Seconds_Behind_Master
].all? { |key| row.key? key }
end
def slave_running?(row)
%w[
Slave_IO_Running
Slave_SQL_Running
].all? { |key| row[key] =~ /Yes/ }
end
def run
if config[:ini]
ini = IniFile.load(config[:ini])
section = ini[config[:ini_section]]
db_user = section['user']
db_pass = section['password']
else
db_user = config[:user]
db_pass = config[:pass]
end
db_host = config[:host]
db_conn = config[:master_connection]
if [db_host, db_user, db_pass].any?(&:nil?)
unknown 'Must specify host, user, password'
end
begin
db = Mysql.new(db_host, db_user, db_pass, nil, config[:port], config[:socket])
results = if db_conn.nil?
db.query 'SHOW SLAVE STATUS'
else
db.query "SHOW SLAVE '#{db_conn}' STATUS"
end
unless results.nil?
results.each_hash do |row|
warn "couldn't detect replication status" unless detect_replication_status?(row)
slave_running = slave_running?(row)
output = if db_conn.nil?
'Slave not running!'
else
"Slave on master connection #{db_conn} not running!"
end
output += ' STATES:'
output += " Slave_IO_Running=#{row['Slave_IO_Running']}"
output += ", Slave_SQL_Running=#{row['Slave_SQL_Running']}"
output += ", LAST ERROR: #{row['Last_SQL_Error']}"
critical output unless slave_running
replication_delay = row['Seconds_Behind_Master'].to_i
message = "replication delayed by #{replication_delay}"
if replication_delay > config[:warn] &&
replication_delay <= config[:crit]
warning message
elsif replication_delay >= config[:crit]
critical message
elsif db_conn.nil?
ok "slave running: #{slave_running}, #{message}"
else
ok "master connection: #{db_conn}, slave running: #{slave_running}, #{message}"
end
end
ok 'show slave status was nil. This server is not a slave.'
end
rescue Mysql::Error => e
errstr = "Error code: #{e.errno} Error message: #{e.error}"
critical "#{errstr} SQLSTATE: #{e.sqlstate}" if e.respond_to?('sqlstate')
rescue StandardError => e
critical e
ensure
db.close if db
end
end
end

Related

Sending data to TCPServer more than one time

I'm new to ruby and I'm trying to make a client to connect to a TCPServer, and it seems that in order to do so I have to call the method close_write every time I finish sending data one way, to let the client/server know that the other end is finished sending data. Whenever I do that then Im not able to write info to the server or client again because the socket is not opened for writing anymore.
This is my code:
client.rb
require "socket"
socket = TCPSocket.open("localhost", 6666)
loop do
input = gets.chomp
socket.puts input # Send data to server
socket.close_write
while(line = socket.gets)
puts line
end # Print sever response
break if input=="EXIT"
end
socket.close
server.rb
require "socket"
server = TCPServer.new 6666
data = Hash.new { |hash, key| hash[key] = {} }
WAITING_SET_VALUE = "1"
WAITING_NEW_COMMAND = "0"
loop do
Thread.start(server.accept) do |session|
thread_status ||= WAITING_NEW_COMMAND
....
puts "Entering If..."
if(thread_status == WAITING_NEW_COMMAND) #Check thread status
puts "thread_status == WAITING_NEW_COMMAND"
puts "CHECKING COMMAND.."
case line.strip
when /^set \w* \w* \d{1,7} \d{1,7}$/
puts "COMMAND SET"
thread_status = WAITING_SET_VALUE
lineArr = line.strip.split(" ")
varName = lineArr[1]
flag = lineArr[2]
ttl = lineArr[3]
size = lineArr[4]
puts "END SET EXECUTION"
session.write "Executed"
session.close_write
...
Is there a way to open the socket for writing again, or a better way to do this back and forth connection between server and client without losing context? Thanks!
When designing a client-server protocol, you have to decide:
How a client knows when a response has more lines/data.
How a client knows when a response is complete.
How a client knows when a response is invalid/valid.
How a client knows when there was some type of server error.
A simple approach is for the server to return a response with the number of lines (as in the code below). However, instead, you could use END or something so that the client knows when there is no more data to read. I would strongly suggest looking into tutorials about Protocols.
Save the below into a file called client_server.rb. First, run the server with ruby ./client_server.rb s and then in a separate terminal run the client with ruby ./client_server.rb c. Next, type in list over and over to see the different responses. I added list so that you don't have to type in set w w 1 1 over and over for testing purposes. Check it out and let me know if you have any questions.
# frozen_string_literal: true
require 'socket'
# Use:
# First, run the server: ruby ./client_server.rb s
# Then, run the client: ruby ./client_server.rb c
# Use "netcat localhost 6666" on the command line to test
# without implementing a client.
# Returns false to close this client socket.
# Returns true to continue reading from this client socket.
def handle_client(client_id, client_socket, command)
# TODO: Define some type of client-server Protocol here.
case command
when /^set \w* \w* \d{1,7} \d{1,7}$/
puts "Running command for client #{client_id}: #{command}"
# This is just for testing purposes.
case rand(3)
when 0
client_socket.puts 'lines 0'
when 1
client_socket.puts 'lines 3'
client_socket.puts 'This is line 1.'
client_socket.puts 'This is line 2.'
client_socket.puts 'This is line 3.'
when 2
client_socket.puts 'The set command returned an error.'
end
when 'list'
puts "Responding to client #{client_id} with list of messages."
# This is just for testing purposes.
case rand(3)
when 0
client_socket.puts 'messages 0'
when 1
client_socket.puts 'messages 2'
client_socket.puts 'This is message 1.'
client_socket.puts 'This is message 2.'
when 2
client_socket.puts 'Unable to retrieve messages due to error.'
end
when 'bye'
puts "Killing client #{client_id}."
return false # Disconnect and kill the thread.
else
client_socket.puts "[ERROR] Invalid command: #{command}"
end
client_socket.flush # Flush all output just in case.
true # Continue connection.
end
case ARGV[0].to_s.downcase
when 's' # server
TCPServer.open(6666) do |server_socket|
global_client_id = 1
loop do
Thread.start(global_client_id, server_socket.accept) do |client_id, client_socket|
puts "Accepting new client #{client_id}."
loop do
command = client_socket.gets
if command.nil?
puts "Client #{client_id} disconnected manually."
break
end
command = command.strip
keep_alive = handle_client(client_id, client_socket, command)
break unless keep_alive
end
client_socket.close
end
global_client_id += 1
end
end
when 'c' # client
TCPSocket.open('localhost', 6666) do |socket|
puts '[Commands]'
puts 'set <word> <word> <num> <num> Run set command.'
puts 'list Get list of messages.'
puts 'exit, bye Exit the client.'
puts
loop do
print '> '
input = $stdin.gets.strip
# TODO: Define some type of client-server Protocol here.
case input
when /EXIT|BYE/i
socket.puts 'bye'
socket.flush
break
when /\Aset .+\z/
socket.puts input
socket.flush
response = socket.gets
if response.nil?
puts 'Server is not running anymore! Disconnecting.'
break
end
response = response.strip
match_data = response.match(/\Alines (?<lines>\d+)\z/)
if match_data
line_count = match_data[:lines].to_i
puts "Received #{line_count} lines from server."
1.upto(line_count) do |i|
line = socket.gets
puts ">> Resp[#{i}] #{line}"
end
else
# Can check for "response == 'ERROR'" or something.
puts "Server error or invalid response from server: #{response}"
end
when 'list'
socket.puts input
socket.flush
response = socket.gets
if response.nil?
puts 'Server is not running anymore! Disconnecting.'
break
end
response = response.strip
match_data = response.match(/\Amessages (?<messages>\d+)\z/)
if match_data
message_count = match_data[:messages].to_i
puts "Received #{message_count} messages from server."
1.upto(message_count) do |i|
line = socket.gets
puts ">> Resp[#{i}] #{line}"
end
else
# Can check for "response == 'ERROR'" or something.
puts "Server error or invalid response from server: #{response}"
end
else
puts "Invalid command: #{input}"
end
end
end
else
puts "Pass in 'c' for client or 's' for server."
end

Ruby Mechanize Stops Working while in Each Do Loop

I am using a mechanize Ruby script to loop through about 1,000 records in a tab delimited file. Everything works as expected until i reach about 300 records.
Once I get to about 300 records, my script keeps calling rescue on every attempt and eventually stops working. I thought it was because I had not properly set max_history, but that doesn't seem to be making a difference.
Here is the error message that I start getting:
getaddrinfo: nodename nor servname provided, or not known
Any ideas on what I might be doing wrong here?
require 'mechanize'
result_counter = 0
used_file = File.open(ARGV[0])
total_rows = used_file.readlines.size
mechanize = Mechanize.new { |agent|
agent.open_timeout = 10
agent.read_timeout = 10
agent.max_history = 0
}
File.open(ARGV[0]).each do |line|
item = line.split("\t").map {|item| item.strip}
website = item[16]
name = item[11]
if website
begin
tries ||= 3
page = mechanize.get(website)
primary1 = page.link_with(text: 'text')
secondary1 = page.link_with(text: 'other_text')
contains_primary = true
contains_secondary = true
unless contains_primary || contains_secondary
1.times do |count|
result_counter+=1
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - No"
end
end
for i in [primary1]
if i
page_to_visit = i.click
page_found = page_to_visit.uri
1.times do |count|
result_counter+=1
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name}"
end
break
end
end
rescue Timeout::Error
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - Timeout"
rescue => e
STDERR.puts e.message
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - Rescue"
end
end
end
You get this error because you don't close the connection after you used it.
This should fix your problem:
mechanize = Mechanize.new { |agent|
agent.open_timeout = 10
agent.read_timeout = 10
agent.max_history = 0
agent.keep_alive = false
}

OptParser does not return options

I have this code sample:
#!/usr/bin/env ruby
require_relative File.expand_path('../../lib/argosnap', __FILE__)
require 'optparse'
options = {}
opt_parser = OptionParser.new do |opt|
opt.banner = "argosnap #{Argosnap::VERSION} ( http://github/atmosx/argosnap )\nUsage: argosnap [OPTIONS]"
opt.separator ""
opt.separator " version: dislay version"
opt.separator " install: installs 'config.yml' and launchd script"
opt.separator " balance: check picodollars"
opt.separator ""
opt.on("-v","--version","display version") do |version|
options[:version] = version
end
opt.on("-c","--config [TYPE]", String, "install configuration files") do |config|
options[:config] = config
end
opt.on("-b","--balance","executes 'argosnap' and displayes notifications") do |balance|
options[:balance] = balance
end
opt.on("-h","--help","help") do
puts opt_parser
end
end
begin
opt_parser.parse!
rescue OptionParser::InvalidOption => e
puts "No such option! Type 'argosnap -h' for help!"
exit
end
case ARGV[0]
when "version"
puts Argosnap::VERSION
when "config"
Argosnap::Install.new.config
when "balance"
b = Argosnap::Fetch.new.balance
puts "Current balance (picodollars): #{b}"
else
puts "Type: 'argosnap -h' for help!"
end
My problem is that options hash is empty. It's like if it doesn't accept the options[:var] = var defined inside the OptParser class. I'd like to use -v and --version in my program to make it more unix-like.
I'm using ruby-2.0.
UPDATE: The way it is the code works I've tried changing when "version" with when '-v' or when options[:version] which seemed the best approach to me, but nothing worked.
when you write case ARGV[0] you are totally ignoring the opt_parser...
ARGV[0] is the first word in the command line. The whole point of opt_parser is that you don't look at ARGV:
if options[:version]
puts Argosnap::VERSION
elsif options[:config]
Argosnap::Install.new.config
elsif options[:balance]
b = Argosnap::Fetch.new.balance
puts "Current balance (picodollars): #{b}"
else
puts "Type: 'argosnap -h' for help!"
end

"negotiation timeout" when using em-ssh to connect multiple hosts in parallel

I use em-ssh gem (which is a net-ssh adapter for EventMachine) to execute some shell commands on multi remote hosts. Commands are executed successfully on the first host, but failed on other hosts, it complains “EventMachine::Ssh::ConnectionTerminated”
and “EventMachine::Ssh::NegotiationTimeout”.
It seems that these hosts are competing for something maybe the port? Can someone explains this?
def em_ssh_connection(ssh_host,ssh_user,ssh_password,&cb)
EM.run do
EM::Ssh.start("#{ssh_host}","#{ssh_user}",:password => "#{ssh_password}") do|connection|
connection.errback do |err|
#logger.info "#{err} (#{err.class})"
EM.stop
end
connection.callback do |ssh|
#logger.info "succcessfully connected with #{ssh_host},#{ssh_user},#{ssh_password}"
yield ssh
ssh.close
EM.stop
end
end
end
end
def search_servers(ssh_host,ssh_user,ssh_password)
em_ssh_connection(ssh_host,ssh_user,ssh_password) {|ssh|
command = 'sed -n \'/.*server s1 [0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/=\' '+"#{#haproxy_conf}"
myarray = em_ssh_exec(ssh,command)
if myarray[2] == 0
if myarray[0] != ""
#logger.info "all tcp servers(lines): #{myarray[0]}"
lines = myarray[0].split("\n")
if lines.length >0
start_line = lines[0].to_i
stop_line = lines[lines.length-1].to_i
#logger.info "start_line:#{start_line},stop_line:#{stop_line}"
delete_tcp_upstream(ssh,start_line,stop_line)
else
end
else
#logger.info "the stream field in proxy file is empty"
end
else
#logger.info "search all servers command failed:#{myarray[1]}"
end
}
end
def clear_haproxy(&cb)
search_servers(#ssh_host1,#ssh_user1,#ssh_password1)
search_servers(#ssh_host2,#ssh_user2,#ssh_password2)
cb.call
end

Using Ruby and SQL SMO for Script Automation

I need to create a script in ruby to get all the database objects (tables,views,sps, functions, etc) and be able to create files for each of the db objects.
I would like to be able to implement this solution in ruby and use some sort of Win32 class may be?.
I am using SQL Server 2008 R2. Not ruby on rails of course.
# == Name
# SQL Server Library
# == Author
# Maverick
# == Synopsis
# ADO SQL Server Library
# == Notes:
# Modify the following global variables in order to set up an execution environment
# sql_str: This is the SQL CMD command option and arguments -> Change the -U and -P arguments for -E to enable integrated security
# http://rubyonwindows.blogspot.com/2007/03/ruby-ado-and-sqlserver.html
Thread.abort_on_exception = true
require 'win32ole'
require 'win32api'
CoInitialize = Win32API.new('ole32', 'CoInitialize', 'P', 'L')
# This class manages database connection and queries
class SqlServer
attr_accessor :connection, :data, :fields
def initialize
#connection = nil
#data = nil
#cmd_time_out = 900
end
#opens a database connection using integrated security
def open(server,database)
connection_string = "Provider=SQLOLEDB.1;"
connection_string << "Persist Security Info=False;"
connection_string << "Integrated Security=SSPI;"
connection_string << "Initial Catalog=#{database};"
connection_string << "Data Source=#{server};"
connection_string << "Network Library=dbmssocn"
CoInitialize.call( 0 )
if server.eql?(nil) or database.eql?(nil) or server.eql?('') or database.eql?('') then
raise Exception, "Application Error: Server or Database parameters are missing"
end
begin
#connection = WIN32OLE.new('ADODB.Connection')
#connection.ConnectionString = connection_string
#connection.open
rescue Exception => e
#connection.Errors.Count.times { |x|
show_ado_error(#connection.Errors)
}
raise Exception, "Application Error: #{e.message} \n Can't open a connection with the server. Verify user credentials"
end
end
def get_connection
return #connection
end
#executes a query without returning any rows
def execute_non_query(query)
begin
command = WIN32OLE.new('ADODB.Command')
command.CommandType = 1
command.ActiveConnection = #connection
command.CommandText = query
command.CommandTimeOut = #cmd_time_out
result = command.Execute
if #connection.Errors.Count > 1 then
raise Exception,"ADODB Connection contains errors"
end
rescue Exception => e
show_ado_error(#connection.Errors)
raise Exception, "Application Error: #{e.message} \n Can't execute query. Verify sql syntax"
end
return result
end
#prints ado db errors using ado connection error property
def show_ado_error(obj)
obj.Count.times { |x|
puts "#{x}. ADODB Error Number: " + #connection.Errors(x).Number.to_s
puts "#{x}. ADODB Generated By: " + #connection.Errors(x).Source
puts "#{x}. ADODB SQL State: " + #connection.Errors(x).SQLState
puts "#{x}. ADODB Native Error: " + #connection.Errors(x).NativeError.to_s
puts "#{x}. ADODB Description: " + #connection.Errors(x).Description
}
end
#executes a query returning an array of rows
def execute_query(sql_query)
# Create an instance of an ADO Record set
begin
record_set = WIN32OLE.new('ADODB.Recordset')
# Open the record set, using an SQL statement and the
# existing ADO connection
record_set.open(sql_query, #connection)
# Create and populate an array of field names
#fields = []
record_set.fields.each do |field|
#fields << field.Name
end
begin
# Move to the first record/row, if any exist
record_set.movefirst
# Grab all records
#data = record_set.getrows
rescue
#data = []
end
record_set.close
# An ADO Recordset's GetRows method returns an array
# of columns, so we'll use the transpose method to
# convert it to an array of rows
#data = #data.transpose
rescue
raise Exception, "Application Error: Can't execute query. Verify SQL Query syntax"
end
end
def close
#connection.Close
end
end

Resources