双六工場日誌

平凡な日常を淡々と綴ります。

Perl から Zabbix API を叩いてみる。

今日は Zabbix-JP の勉強会があるので、そのネタ提供ということで、Perl から Zabbix API を叩くサンプルスクリプトを書いてみました。

Zabbix-JP のパッケージは、RHEL5/CentOS5 向けに作られていますが、これらのディストリビューションだとデフォルトのRubypythonのバージョンが古いため、Zabcon を始めとした Zabbix API 向けの ライブラリがそのままでは動きません。
そのため、Perl を使って Zabbix API を叩くという需要もそれなりにあるとは思いますが、ちょっとぐぐってみたところ、本家のコミュニティに Zabbix 1.8 API and Perl というのがあるぐらいで Perl の情報はあまり見つからず…
なので自分でサンプルをつくってみました。*1 *2
最後に出力サンプルをつけていますが、このスクリプトがやってくれるのは Zabbix API を直接叩いて、その結果を標準出力に吐き出すだけです。

■使用例

$ perl get_zabbix_data.pl --method host.get
$ perl get_zabbix_data.pl --method item.get --zabbix_server 192.168.1.2 --limit 1 --filter '{"hostid":["10001","10017"],"description":"Buffers memory"}'

■前提条件

  • Zabbix バージョン: 1.8.3 (Zabbix-JP パッケージ使用)
  • Perl の標準モジュールに加えてJSONモジュールが必要(CPANから適宜インストールをしてください。)
  • Zabbix API が有効になっているアカウント (Admin もインストール時は API 有効ではないので注意!)

■コマンドラインオプション

  • "--method" だけが必須のオプションで、あとのオプションには適当にデフォルト値を入れています。今のところ、"host.get", "item.get", "graph.get" など比較的単純な "get" メソッドのみ対応です。
  • "--zabbix_server" はその名の通りのオプションです^^ デフォルトは "localhost" になっています。
  • "--limit" は取得するアイテム数の上限を設定するオプションです。デフォルトは"10"にしてあります。
  • "--filter" は、取得アイテムフィルタリングオプションなのですが、JSONをそのまま渡してください。どういうフィルタリングができるのかは、本家のAPIドキュメント参照してください^^;
  • また、Zabbix にアクセスするユーザはデフォルトではユーザ名: "Admin"、パスワード: "Zabbix" を使いますが、"--user", "--password" オプションを使って、別のユーザアカウントを使うこともできます。(使用するアカウントの API アクセスを有効化するのを忘れないようにしてください。)

このコードは、そのまま使えるものでもないので、適当に切り貼りして使ってもらってかまいません。ですが、「こういう風に使ったよ〜」みたいな話だけ聞かせてもらえると、僕が喜びます^^
何かあれば、twitterで話しかけてもらえればベストエフォートで対応しますw


■スクリプト本体(get_zabbix_data.pl)

#!/usr/bin/perl
# Last Modified: 2010/12/18
# Author: Seiichiro, Ishida <twitterID: @sechiro>

use strict;
use warnings;
use Socket;
use JSON;
use Getopt::Long;

# JSON OO Interface
my $json = JSON->new->allow_nonref;

# Command Line Options
my $zabbix_server = "localhost";
my $user = "Admin"; # Zabbix default
my $password = "zabbix"; # Zabbix default
my $useragent = "zabbi-tan";
my $method;
my $limit = 10; # 一度に取得するアイテム数
my $filter; # ex)   --method host.get --filter '{"host":"Zabbix server"}'
            #       --method item.get --filter '{"hostid":["10001","10017"],"description":"Buffers memory"}'
my $opt_parse = GetOptions (
    "zabbix_server=s" => \$zabbix_server,
    "user=s"        =>  \$user,
    "password=s"    =>  \$password,
    "useragent=s"   =>  \$useragent,
    "method=s"      =>  \$method,
    "limit=i"       =>  \$limit,
    "filter=s"      =>  \$filter,
);


die "No method specified!" unless ( defined($method) );
my $filter_hash = $json->decode( $filter ) if ( defined($filter) );

# Authentication
my $json_auth = $json->encode( create_auth_request_hash($user, $password) );
my $rpc_auth_request = create_rpc_request($zabbix_server, $useragent, $json_auth);
my $json_auth_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_auth_request) );
my $auth = $json_auth_result->{result};
my $id = $json_auth_result->{id};
$id++;

# Get Zabbix Data
my $json_data = $json->encode( create_get_request_hash($auth, $id, $method, $filter_hash, $limit) );
my $rpc_request = create_rpc_request($zabbix_server, $useragent, $json_data);
my $json_result = $json->decode( get_zabbix_data($zabbix_server, $rpc_request) );

# output
my $pretty_printed = $json->pretty->encode( $json_result );
print $pretty_printed;

exit;

sub create_auth_request_hash{
    my $user = shift;
    my $password = shift;
    
    my %params = (
        user        =>  $user,
        password    =>  $password,
    );
    my %auth_request = (
        auth        =>  undef,
        method      =>  'user.authenticate',
        id          =>  0,
        params      =>  \%params,
        jsonrpc     =>  '2.0',
    );
    return \%auth_request;
}

sub create_get_request_hash{ #for host.get etc.
    my $auth = shift;
    my $id = shift;
    my $method = shift;
    my $filter_hash = shift;
    my $limit = shift;
    
    my %params = (
        output      =>  'extend',
        limit       =>  $limit,
        filter      =>  $filter_hash,
    );

    my %get_request = (
        auth        =>  $auth,
        method      =>  $method,
        id          =>  $id,
        params      =>  \%params,
        jsonrpc     =>  '2.0',
    );
    return \%get_request;
}

sub create_rpc_request{
    my $zabbix_server = shift;
    my $useragent = shift;
    my $json_data = shift;
    my $header = "POST /zabbix/api_jsonrpc.php HTTP/1.1\n"
        . "Accept: */*\n"
        . "Connection: close\n"
        . "Content-Type: application/json-rpc\n";
    
    $header .= "User-Agent: " . $useragent ."\n";
    $header .= "Content-Length: " . length($json_data) . "\n";
    $header .= "Host: " . $zabbix_server . "\n\n";

    return $header . $json_data;
}

sub get_zabbix_data{
    use Socket;
    my $zabbix_server = shift;
    my $rpc_request = shift;
    
    my $sock;
    socket( $sock, PF_INET, SOCK_STREAM, getprotobyname('tcp') )
        or die "Cannot create socket: $!";
    my $packed_remote_host = inet_aton( $zabbix_server )
    or die "Cannot pack $zabbix_server: $!";

    my $remote_port = 80; 

    # ホスト名とポート番号をパック
    my $sock_addr = sockaddr_in( $remote_port, $packed_remote_host )
        or die "Cannot pack $zabbix_server:$remote_port: $!";

    connect( $sock, $sock_addr )
        or die "Cannot connect $zabbix_server:$remote_port: $!";

    # 書き込みバッファリングをしない。
    my $old_handle = select $sock;
    $| = 1; 
    select $old_handle;

    print $sock "$rpc_request";
    
    shutdown $sock, 1; # 書き込みを終了する。

    
    # データの読み込み
    my $json_result;
    while( my $line = <$sock> ){
        if ( $line =~ /jsonrpc/ ){
            $json_result = $line;
        }
    }
    close $sock;
    return $json_result;
}

__END__

■実行結果サンプル

$ perl get_zabbix_data.pl --method host.get --filter '{"host":"Zabbix server"}'
{
   "jsonrpc" : "2.0",
   "id" : 1,
   "result" : [
      {
         "dns" : "",
         "maintenances" : [
            {
               "maintenanceid" : "0"
            }
         ],
         "outbytes" : "0",
         "snmp_error" : "",
         "ipmi_ip" : "",
         "ipmi_authtype" : "0",
         "ipmi_username" : "",
         "ipmi_error" : "",
         "useipmi" : "0",
         "port" : "10050",
         "maintenanceid" : "0",
         "errors_from" : "0",
         "ipmi_password" : "",
         "useip" : "1",
         "ipmi_privilege" : "2",
         "maintenance_type" : "0",
         "maintenance_status" : "0",
         "ip" : "127.0.0.1",
         "status" : "0",
         "lastaccess" : "0",
         "hostid" : "10017",
         "ipmi_port" : "623",
         "ipmi_errors_from" : "0",
         "ipmi_available" : "0",
         "disable_until" : "0",
         "proxy_hostid" : "0",
         "error" : "",
         "snmp_disable_until" : "0",
         "maintenance_from" : "0",
         "available" : "1",
         "snmp_available" : "0",
         "host" : "Zabbix server",
         "inbytes" : "0",
         "snmp_errors_from" : "0",
         "ipmi_disable_until" : "0"
      }
   ]
}

■追記(2010/12/19)

いただいたコメントを参考に"IO::Socket::INET"を使ったバージョンもつくりました。
最後の"sub get_zabbix_data"を以下のコードに差し替えてください。

sub get_zabbix_data{
    use IO::Socket::INET;
    my $zabbix_server = shift;
    my $rpc_request = shift;
    
    my $sock = IO::Socket::INET->new(PeerAddr => "$zabbix_server",
                                     PeerPort => 'http(80)',
                                     Proto    => 'tcp',
        ) or die "Cannot create socket: $!";

    $sock->print("$rpc_request");
    
    # データの読み込み
    my $json_result;
    while( my $line = $sock->getline ){
        if ( $line =~ /jsonrpc/ ){
            $json_result = $line;
        }
    }
    return $json_result;
}

*1:こういう経緯で作ったものなので、LWP::UserAgent ではなく 単純な Socket 通信を使っています。

*2:また、Socket 通信に関しては、[http://d.hatena.ne.jp/perlcodesample/20090407/1238923759:title=サンプルコードによるPerl入門:ソケットを作成する]を全面的に参考にさせていただきました。ありがとうございました。