読者です 読者をやめる 読者になる 読者になる

カイワレの大冒険 Third

技術的なことや他愛もないことをたまに書いてます

Rubyでリトライとか、SSH経由でコマンドとか

プログラミング プログラミング-Ruby

実家にいるとどうも時間持て余してしまうので、昔書いたスクリプトを備忘録的に残しておく。一年以上前に書いたやつだな。。。

リトライ

たとえば、特定のURLに対して並列でGETしたい、ただステータスコードがおかしかったりしたら、キャッチしておきたいようなとき。CDNを暖気運転させるときに使いました。

require 'net/http'
require 'parallel'
require 'timeout'

BASE_URL = 'example.org'
REQUEST_PATH = '/'
NUM_THREADS = 18
# zcat /var/log/hogehoge/access_log.20141228.gz | perl -anle 'print $F[6]' | head -n 100 > request_path.list とかで取得しとく
PATH_LIST = './request_path.list'

class HttpClient
  def get_url(base_url, request_path)
    begin
      failed ||= 0
      timeout(10) do
        response_status = Net::HTTP.get_response(base_url, request_path)
        validate_status_code(response_status.code, request_path)
      end
      rescue Timeout::Error
        puts "Timeout Occur! URL: #{base_url}#{request_path}"
      rescue RuntimeError
        failed += 1
        puts "status code error: retry is started"
        retry if failed < 3
    end
  end

  def validate_status_code(code, request_path)
    if code.to_i == 200 || code.to_i == 404 || code.to_i == 400
      #puts "#{request_path}, #{code}"
    else
      raise "status code is NOT 200, but #{code}, #{request_path}"
    end
  end
end

puts "#{Parallel.processor_count} procesor(s)"

request_paths = []
File.read(PATH_LIST).each_line do |line|
  request_paths << line.chop
end

client = HttpClient.new()
Parallel.map(request_paths, :in_threads => NUM_THREADS) {|path|
  client.get_url(BASE_URL, path)
}

コツは以下ですね。

    begin
      failed ||= 0
      <<中略>>
      rescue Timeout::Error
        puts "Timeout Occur! URL: #{base_url}#{request_path}"
      rescue RuntimeError
        failed += 1
        retry if failed < 3
    end

failedを初期化して、3未満だったらretryする。以上。

SSH経由でコマンド

Rubyの場合はSSH経由だとnet/sshが主流なのだろうか。

#!/usr/bin/env ruby

require 'systemu'
require "open3"
require 'logger'
require 'net/ssh'

@log = Logger.new(STDOUT)
@log.level = Logger::INFO

def get_master_binlog(port="3306")
  @log.info "target port: #{port}"
  o, e, s = Open3.capture3("mysql -uhogehoge -S /var/run/hogehoge/mysql#{port}.sock -e 'show master logs' | cut -f 1")
  new_o = o.split("\n")
  target_binlogs =  new_o.select {|v| v if /mysql/ =~ v}
end

def execute_ssh_command(host="localhost", user="mysql", command="hostname")
  begin
    Net::SSH.start(host, user) do |ssh|
      stdout = ''
      stderr = ''
      ssh.exec!(command) do |channel, stream, data|
        stdout << data if stream == :stdout
        stderr << data if stream == :stderr
      end
      stdout
    end
  rescue => e
    p e.message
  end
end

def is_existance_remote_file(remote_host, file_path)
  is_existed = false

  existance = execute_ssh_command(remote_host, 'mysql', "ls -l #{file_path}")
  if existance.empty?
    @log.info "not found: #{file_path}"
  else
    @log.info "Found: #{file_path}"
    is_existed = true
  end

  is_existed
end

target_port = [3306, 3307]

target_port.each do |port|
  get_master_binlog(port).each do |binlog|
    if is_existance_remote_file('XXX.XXX.XXX.XXX', "/backup/binlog/#{binlog}*")
      puts "existed"
    end
  end
end

複数ポートに対応したbinlogがリモートホストにあるか調べたかったらしい。

以下の部分すな。

  begin
    Net::SSH.start(host, user) do |ssh|
      stdout = ''
      stderr = ''
      ssh.exec!(command) do |channel, stream, data|
        stdout << data if stream == :stdout
        stderr << data if stream == :stderr
      end
      stdout
    end
  rescue => e
    p e.message
  end

まぁ、またいつか参考にするかもしれないし、備忘録ということで。