Status Code 303 - See Other

サーバサイド、iOS・アンドロイドアプリ、インフラレベルの話まで幅広くやってます。情報の誤りや指摘・意見などは自由にどうぞ。

Javaアプリから Zabbix API を使う

概要

前回 curl で疎通確認を行ったが、今回はこれらを Java で実装する。
前回記事:Zabbix API を使ってみる - Status Code 303 - See Other

Zabbix API は、JSON 形式で API と通信するため、JSON 形式で通信できるようセットアップする必要がある。
また、Zabbix API は、API の種類が多く、そのリクエストパラメータとそのレスポンスデータの種類も
オブジェクトリスト・オブジェクト・文字リスト・基本データ型など多種多様なので、これらをなるべく扱えるよう設計する。

また、最後には拡張方法の方針について触れる。

ライブラリ

今回は、Jersey (Jersey)を用いる。
JAX-RS (JAX-RS - Wikipedia)という、RESTFUL なアーキテクチャを実装するための API として有名。
しかし、jersey には、クライアントの機能を提供するライブラリもあり、それらは結構使い易いらしい。
参考:HTTPクライアントとして使うjersey-client – Akira Koyasu's WebLog

導入

まずは、ライブラリをダウンロードして、それらに対してビルドパスを設定する。
まずは、HTTP クライアント用ライブラリの導入。

Jersey-client 本体
  • jersey-client.jar
Jersey-client に必要なライブラリ
  • jersey-common.jar
  • javax.ws.rs-api-2.0.1.jar
  • javax.annotation-api-1.2.jar
  • javax.inject-2.4.0-b31.jar
  • hk2-api-2.4.0-b31.jar
  • hk2-locator-2.4.0-b31.jar
  • hk2-utils-2.4.0-b31.jar
  • jersey-guava-2.22.1.jar
  • jersey-entity-filtering-2.22.2.jar
  • jersey-media-json-jackson-2.22.2.jar
  • jackson-core-2.7.1.jar
  • jackson-databind-2.5.4.jar
  • jackson-jaxrs-base-2.5.4.jar
  • jackson-jaxrs-json-provider-2.5.4.jar
JSON を扱うためのライブラリ

上記ライブラリだけでも HTTP 通信は可能だが、JSON には対応できないらしく、専用ライブラリを導入する必要がある。
java - MessageBodyWriter not found for media type=application/json - Stack Overflow

  • jackson-annotations-2.5.4.jar
  • jackson-module-jaxb-annotations-2.5.4.jar
Lombok

ボイラープレートコード削減ライブラリ。
Lombok ライブラリ - Status Code 303 - See Other

実装内容

今回作成したのは、大きく分けて3つ

  • Zabbix API クラス (API 通信コントロール)
  • パラメータ構築クラス (リクエストデータ格納)
  • Zabbix プロパティ (設定ファイル)

この他、API に応じて以下を作成する。

  • API 結果型 (値格納)

Zabbix API クラス

今回は、実装量がある程度多いため、コメントは結構適当。

package api.zabbix;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

import lombok.Data;
import resources.properties.Property;

public class ZabbixAPIAccess {

	// Zabbix の設定ファイル
	static Property property = new Property("zabbix");

	// トークン格納先
	private String token;

	// トークンの管理を担当、結果はレスポンス形式。
	public Response request(ZabbixParam params) throws JsonProcessingException, IOException {
		// トークン取得条件。本来はタイムアウトとか考慮だろうけど、かなり期間長いみたいなので適当
		if (token == null) {
			token = getToken();
		}
		Map<String, Object> p = getWrappingRequest(params);
		p.put("auth", token);
		return getResponse(p);
	}

	// レスポンスデータをオブジェクト形式にマッピング。第1引数にリクエストデータ、第2引数にマッピングクラス記述
	public <T> T request(ZabbixParam params, Class<T> klass) throws JsonProcessingException, IOException {
		ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		JavaType type = mapper.getTypeFactory().constructParametrizedType(ZabbixResponse.class, ZabbixResponse.class, klass);
		ZabbixResponse<T> value = mapper.readValue((InputStream) request(params).getEntity(), type);
		return value.result;
	}

	// レスポンスデータをリスト形式にマッピング。第1引数にリクエストデータ、第2引数に各要素に適用するマッピングクラス記述
	public <E> List<E> requestWithList(ZabbixParam params, Class<E> componentKlass)
			throws JsonProcessingException, IOException {
		ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		TypeFactory factory = mapper.getTypeFactory();
		ZabbixResponse<List<E>> value = mapper.readValue((InputStream) request(params).getEntity(), factory
				.constructParametrizedType(ZabbixResponse.class, ZabbixResponse.class, factory.constructCollectionType(ArrayList.class, componentKlass)));
		return value.result;
	}

	// パラメータをAPI 仕様の形式にラッピング
	private Map<String, Object> getWrappingRequest(ZabbixParam params) {
		Map<String, Object> result = new HashMap<>();
		result.put("jsonrpc", "2.0");
		result.put("method", params.getMethod());
		result.put("id", 1);
		result.put("params", params.getParameters());
		return result;
	}

	// 通信
	private Response getResponse(Map<String, Object> params) {
		return ClientBuilder.newClient().target(property.getString("zabbix.api.url")).request()
				.post(Entity.entity(params, MediaType.APPLICATION_JSON));
	}

	// トークン取得
	private String getToken() throws JsonProcessingException, IOException {
		ZabbixParam loginParams = new MapParam(new ZabbixParam("user.login"))
				.put("user", property.getString("zabbix.user")).put("password", property.getString("zabbix.password"));
		return new ObjectMapper().readTree((InputStream) getResponse(getWrappingRequest(loginParams)).getEntity())
				.get("result").asText();
	}

	// 一時的なレスポンスデータ格納先。使わないため private
	@Data
	private static class ZabbixResponse<T> {
		private String jsonrpc;
		private T result;
		private int id;
	}
}

パラメータ構築クラス

メソッドとパラメータを格納。
パラメータがリストやマップの場合は、下記 Decorator クラスを使った方が良い。

package api.zabbix;

import lombok.Data;

@Data
public class ZabbixParam {

	// (Zabbix APIの) メソッド名
	private final String method;
	// リクエストパラメータ
	private Object parameters;

}

ZabbixParameter がMapだったときに活躍しそうな、Decorator クラス。

package api.zabbix;

import java.util.LinkedHashMap;
import java.util.Map;

public class MapParam extends ZabbixParam {
	
	public MapParam(ZabbixParam param){
		super(param.getMethod());
		super.setParameters(new LinkedHashMap<String, Object>());
	}
	
	@SuppressWarnings("unchecked")
	public MapParam put(String key, Object value) {
		((Map<String, Object>)super.getParameters()).put(key, value);
		return this;
	}
}

ZabbixParameter が List だったときに活躍しそうな、Decorator クラス。

package api.zabbix;

import java.util.ArrayList;
import java.util.List;

public class ListParam<T> extends ZabbixParam {

	public ListParam(ZabbixParam param){
		super(param.getMethod());
		super.setParameters(new ArrayList<T>());
	}
	
	@SuppressWarnings("unchecked")
	public ListParam<T> add(T elements) {
		((List<T>)super.getParameters()).add(elements);
		return this;
	}
}

利用方法

今回は、例として次の流れでロジックを構成する。

  • ホストグループ作成
  • 作成したホストグループ情報取得
  • 作成したホストグループ削除

対応する Zabbix API リファレンスはこちら。(※対応 Zabbix バージョン 2.4)

なお、API 仕様はバージョンによって、変わることがあるので注意すること。

Zabbix 設定ファイル (zabbix.properties)

API 通信できるユーザ名・そのパスワード、そして API 問い合わせ先の設定。

zabbix.user = xxxx
zabbix.password = yyyy

zabbix.api.url = http://xxx.xxx.com/zabbix/api_jsonrpc.php
値格納クラス

API リファレンスから、どんなデータが返ってくるか分かるので、それら用のマッピングクラスを作成。

  • ホストグループクラス (ホストグループ取得に使う)
package api.zabbix.vo;

import com.fasterxml.jackson.annotation.JsonProperty;

public class HostGroup {

	// JSON では groupid 要素をココにマッピング
	@JsonProperty("groupid")
	public String id;
	
	// ホストグループ名
	public String name;

	public String internal;
}
  • ホストグループ ID リスト格納クラス。(ホストグループ作成時・削除時使用)
package api.zabbix.vo;

import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;

public class HostGroupIds {

	@JsonProperty("groupids")
	public List<String> groupIds;
}
ロジッククラス

上記クラスを利用して、以下のようにロジックに記述する。

package api.zabbix;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;

import api.zabbix.vo.HostGroup;
import api.zabbix.vo.HostGroupIds;

public class Main {

	public static void main(String[] args) throws JsonProcessingException, IOException {

		ZabbixAPIAccess api = new ZabbixAPIAccess();

		// ホストグループ作成
		ZabbixParam createHostGroupParams = new MapParam(new ZabbixParam("hostgroup.create")).put("name", "test");
		HostGroupIds createValue = api.request(createHostGroupParams, HostGroupIds.class);

		// ホストグループ一覧取得 (フィルターで上記作成ホストグループだけ取得)
		Map<String, Object> filter = new HashMap<>();
		filter.put("name", "test");
		ZabbixParam getHostGroupParams = new MapParam(new ZabbixParam("hostgroup.get")).put("output", "extend").put("filter", filter);
		List<HostGroup> getValue = api.requestWithList(getHostGroupParams, HostGroup.class);
		System.out.println(getValue);
		
		// 上記のホストグループ削除
		ZabbixParam deleteHostGroupParams = new ListParam<String>(new ZabbixParam("hostgroup.delete")).add(createValue.groupIds.get(0));
		HostGroupIds deleteValue = api.request(deleteHostGroupParams, HostGroupIds.class);
		System.out.println(deleteValue.groupIds);
	}
}

出力結果

[{groupid=59, name=test, internal=0, flags=0}]
[59]

拡張手順

他の API 利用

次の手順で実装。

  1. Zabbix API 仕様を調べる
  2. 値格納用データクラス作成
  3. Main ロジック記述

例:ホストを作成する

  • Zabbix API 仕様

host.create [Zabbix Documentation 2.4]
host.get [Zabbix Documentation 2.4]
host.delete [Zabbix Documentation 2.4]

  • 値格納用クラスの追加
package api.zabbix.vo;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class Host {

	@JsonProperty("hostid")
	public String id;

	public int available;
	
	public String name;
	
	public String host;
	
	@JsonProperty("status")
	public String status;
}
package api.zabbix.vo;

import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;

public class HostIds {

	@JsonProperty("hostids")
	public List<String> ids;
}

ロジックの記述(追加分)

		// ホスト作成
		List<Map<String, Object>> interfaces = new ArrayList<>();
		Map<String, Object> if0 = new HashMap<>();
		if0.put("type", 1);
		if0.put("main", 1);
		if0.put("useip", 1);
		if0.put("ip", "127.0.0.1");
		if0.put("dns", "");
		if0.put("port", "10050");
		interfaces.add(if0);
		List<Map<String, Object>> groups = new ArrayList<>();
		Map<String, Object> group = new HashMap<>();
		group.put("groupid", createValue.groupIds.get(0)); // hostgroup.create から取得
		groups.add(group);
		ZabbixParam createHostParams = new MapParam(new ZabbixParam("host.create")).put("host", "test_server")
				.put("interfaces", interfaces).put("groups", groups);
		HostIds createHostValue = api.request(createHostParams, HostIds.class);
		System.out.println(createHostValue.ids);

		// ホスト取得
		Map<String, Object> filter = new HashMap<>();
		filter.put("host", "test_server");
		ZabbixParam getHostParams = new MapParam(new ZabbixParam("host.get")).put("output", "extend").put("filter", filter);
		List<Host> getValue = api.requestWithList(getHostParams, Host.class);
		System.out.println(getValue);

		// ホスト削除
		ZabbixParam deleteHostParams = new ListParam<String>(new ZabbixParam("host.delete")).add(getValue.get(0).id);
		Response deleteHostValue = api.request(deleteHostParams);
		System.out.println(deleteHostValue.readEntity(String.class));

これでホスト作成・取得・削除も実装できる。

便利なリクエストパラメータを構築するクラスが欲しい

ZabbixParam クラスに対する、Decorator 実装を行う。

  1. ZabbixParam かそれらを継承したクラスを継承したクラス作成
  2. コンストラクタ引数は ZabbixParam を取るもの
  3. 便利なメソッドを追加
  4. ロジッククラスで利用

Zabbix API の通信ログ取得

ZabbixAPIAccess の getResponse で全ての通信を一括しているため、そこでログ取得ライブラリ(Log4j, Logbackなど)で取得する。
実装詳細は、本題からそれるため割愛する。

トークンの取得条件を変更

本実装のトークン管理は、インスタンスに任せているためインスタンスが消滅すると、トークンを毎回取得する。
これをデータベースなどに格納して、頻繁にトークン取得させないようにしたいなら。

  1. トークン管理用データベース・テーブル作成
  2. トークン取得の際にデータベースにトークン/取得時間を格納
  3. データベースからトークン情報取得
  4. トークンが期限内なら使用。そうでなければ、破棄して取得するロジックを記述

ただし、この実装の際には、データベースに大量のトークンが残る可能性あるため
何かしら管理する機構を持たなければならない。