English / Japanese
ここでは eAPI パフォーマンステスト で利用したベンチマークプログラム (Benchmark.java) について紹介する。
Yutaka Yasuda (Arista Square)
24/June/2013 : 最初のリリース。モデル 7048T・EOS 4.12.1 で実験。
1. はじめに
1.1 データ・フォーマット
1.2 HTTP ヘッダ・フォーマット
2. セットアップ
3. プログラム概説
eAPI は Arista スイッチと JSONRPC over HTTP/HTTPS によって EOS の CLI コマンド実行・レスポンスの情報交換を行う。 つまり eAPI を利用するプログラムに必要なものは JSONRPC 形式によるデータの作成と、HTTP/HTTPS による通信である。 また HTTP/HTTPS 通信に際しては Basic 認証が要求されるのでそれに対応する必要もある。
まず show version コマンドを eAPI で実行する際に交換される JSON データを以下に示す。
リクエスト { "id":0, "method":"runCmds", "params":{ "cmds":[ "show version" ], "format":"json", "version":1 }, "jsonrpc":"2.0" } レスポンス { "jsonrpc": "2.0", "result": [ { "modelName": "DCS-7048T-4S-F", "internalVersion": "4.12.1-1275950.EOS4121", "systemMacAddress": "00:1c:73:0c:0c:0c", "serialNumber": "JSH09494949", "memTotal": 2009472, "bootupTimestamp": 1371631462.6494012, "memFree": 121376, "version": "4.12.1", "architecture": "i386", "internalBuildId": "688851f9-4aac-4f78-81ca-69a8114f172a", "hardwareRevision": "02.02" } ], "id": 0 }
このプログラムを用いて実際に交換された HTTP ヘッダを以下に示す。 Basic 認証のための情報があることに注意。
POST リクエスト POST /command-api HTTP/1.1 Accept-Charset: UTF-8 Content-Type: application/json User-Agent: C/YasuTest Host: 192.168.11.111:80 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive Content-Length: 118 Authorization: Basic WjdWu7DwjD2xhw9Di レスポンス HTTP/1.0 200 OK Server: BaseHTTP/0.3 Python/2.7 Date: Fri, 21 Jun 2013 09:58:51 GMT Cache-control: no-store Cache-control: no-cache Cache-control: must-revalidate Cache-control: max-age=0 Cache-control: pre-check=0 Cache-control: post-check=0 Pragma: no-cache Set-Cookie: COMMAND_API_SESSION=0; Path=/ Content-type: application/json Content-length: 542 Connection: close
今回のテストは Java で行ったが、筆者は Java には全く馴染みが無い。 これを実験する学生が Java を希望したので Java で用意したのみであり、サンプルコードなどのひどさはご容赦願いたい。
必要なことは JSONRPC over HTTP / HTTPS への対応であり、Java にはいくつものライブラリが存在する。 今回は JSON-RPC 2.0 を試した。 なおこのライブラリはjson-smart を要求する。
下記のサイトから必要なファイルを取得し、同一のディレクトリに置く。
上記ライブラリのサンプルコードを元に作った show version と show vlan コマンドを実行するだけのサンプルプログラム Benchmark.java を置いておく。 これを取得してコンパイル・実行すれば、以下のように動作するはずである。
コンパイル $ javac -cp .:json-smart-1.1.1.jar:jsonrpc2-base-1.35.jar:jsonrpc2-client-1.14.3.jar:jsonrpc2-server-1.10.1.jar Benchmark.java Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 Note: Benchmark.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. $ 実行 $ java -cp .:json-smart-1.1.1.jar:jsonrpc2-base-1.35.jar:jsonrpc2-client-1.14.3.jar:jsonrpc2-server-1.10.1.jar Benchmark Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 Time(ns) 344672000 <<< JSONRPC over HTTP でコマンドを送り出し、レスポンスを得るための所要時間 4.12.1 <<< show version の結果から "version" キーの内容を抜き出した結果 {"dynamic":false,"status":"active","name":"default","interfaces":{"Ethernet49":{"privatePromoted":false}}} <<< show vlan の結果から "1" の VLAN 情報を抜き出した結果 $
今回用意した show version と show vlan コマンドを実行するだけのサンプルプログラム Benchmark.java について、その主要部分だけ簡単に説明する。
JSONRPC 2.0 ライブラリは残念ながらそれ自身は Basic 認証には対応していない。 つまり http://username@password:192.168.11.111/command-api といった URL を指定すれば自動的に Basic 認証が適用される、といった機能が無い。 そこで Java 標準の Authenticator クラスに介入して Basic 認証を実行させている。 コードではプログラム冒頭にユーザ名・パスワードともにハードコードしている。 (このあたり良い方法とは思えない。おそらく他のライブラリなり手法を使う方が良いと思える。なお https: で始めると HTTPS には対応する。)
// Force Basic authorization final String rpcuser ="admin"; // change to your own username final String rpcpassword ="xxxxxxx"; // change to your own password Authenticator.setDefault( new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication (rpcuser, rpcpassword.toCharArray()); } } );
セッションの準備を行う、ように見えるがここの処理に 50 msec ほど掛かっている。 しかし実際にはこの範囲ではパケットは一切出ず、一体何にこんなに時間が掛かっているかよく分からない。 ここもアクセス先アドレスがハードコードされているので注意。 また、この URL 部分を https: に指定するだけで HTTPS で通信してくれるようになる。
// The JSON-RPC 2.0 server URL URL serverURL = null; try { serverURL = new URL("http://192.168.11.111/command-api"); // change to your own address } catch (MalformedURLException e) { e.printStackTrace(); // need better information } // Create new JSON-RPC 2.0 client session JSONRPC2Session mySession = new JSONRPC2Session(serverURL);
ここで JSONRPC フォーマットのデータを作成する。 実行したい EOS CLI コマンドはこのデータに埋め込む形となる。 サンプルでは show version と show vlan コマンドを入れている。
// Construct new request
String method = "runCmds";
Map
これで以下のような内容の JSON データができあがる。
{"id":0,"method":"runCmds","params":{"cmds":["show version","show vlan"],"format":"json","version":1},"jsonrpc":"2.0"}
JSONRPC2Requestのドキュメントには、自分で JSON フォーマットの文字列を合成して実行させる方法も示されている。 (ただし作った文字列を一度 parse させており、作った文字列がそのまま送られるわけではない模様。)
以下のように send することではじめて TCP のコネクション要求を含めた通信が開始され、結果を受信して response に格納する。
なおこのとき Basic 認証は正しく働いているが、内部的には一度 Authorization: ヘッダなしで POST リクエストを送り、401 authorization fail で断られ、その後自動的に同じ内容の POST request を Authorization: ヘッダつきで送って成功させる、といった処理を裏側で行われている事に注意されたい。
JSONRPC2Response response = null; try { response = mySession.send(request); …
このとき以下のような HTTP データが交換される。
リクエスト POST /command-api HTTP/1.1 Accept-Charset: UTF-8 Content-Type: application/json User-Agent: C/YasuTest Host: 192.168.11.111:80 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive Content-Length: 118 Authorization: Basic WjdWu7DwjD2xhw9Di {"id":0,"method":"runCmds","params":{"cmds":["show version","show vlan"],"format":"json","version":1},"jsonrpc":"2.0"} レスポンス HTTP/1.0 200 OK Server: BaseHTTP/0.3 Python/2.7 Date: Fri, 21 Jun 2013 09:58:51 GMT Cache-control: no-store Cache-control: no-cache Cache-control: must-revalidate Cache-control: max-age=0 Cache-control: pre-check=0 Cache-control: post-check=0 Pragma: no-cache Set-Cookie: COMMAND_API_SESSION=0; Path=/ Content-type: application/json Content-length: 542 Connection: close { "jsonrpc": "2.0", "result": [ { "modelName": "DCS-7048T-4S-F", "internalVersion": "4.12.1-1275950.EOS4121", "systemMacAddress": "00:1c:73:0c:0c:0c", "serialNumber": "JSH09494949", "memTotal": 2009472, "bootupTimestamp": 1371631462.6494012, "memFree": 121376, "version": "4.12.1", "architecture": "i386", "internalBuildId": "688851f9-4aac-4f78-81ca-69a8114f172a", "hardwareRevision": "02.02" } { "sourceDetail": "", "vlans": { "1": { "status": "active", "name": "default", "interfaces": { "Ethernet49": { "privatePromoted": false } }, "dynamic": false }, ..... 中略 .... ], "id": 0 }
正しくレスポンスが得られておれば、result の中に結果がリストとして入っている。 これを取り出すための記述は以下のようになる。 上の HTTP でのやりとりと、下のコードのそれぞれがどのように対応しているのか比較すると良い。
if (response.indicatesSuccess()) { JSONArray resp = (JSONArray)response.getResult(); JSONObject obj = (JSONObject)resp.get(0); System.out.println(obj.get("version")); obj = (JSONObject)resp.get(1); JSONObject vlans = (JSONObject)obj.get("vlans"); JSONObject vlan = (JSONObject)vlans.get("1"); System.out.println(vlan); …
これで結果としては以下のような文字列が得られる。
4.12.1 {"dynamic":false,"status":"active","name":"default","interfaces":{"Ethernet49":{"privatePromoted":false}}}