Skip to content

Programming OpenStack via Java

Masanori ITOH edited this page Dec 25, 2013 · 2 revisions

本記事は、OpenStack Advent Calendar 2013 JP の26日目のエントリです。 一覧はこちら

私は昨年はコミュニティネタのみだったので、今年は技術ネタで行きます。

OpenStack Java SDK とは?

名前がそのまま説明になっていますね。
OpenStack を使う(プログラムする)ためのI/F(API)のJava言語binding(の1つ)です。

OpenStack的には、12/16に中井さんがサンプルを紹介している Python biding が標準的かと思いますが、
OpenStack Project の Wiki のSDK一覧にあるように、他にもたくさんあります。
変わったところとしては、 12/15に中島怪鳥が紹介しているように Common Lisp binding もあったりとか。
余談ですが、私はひそかに Erlang binding が欲しいです(笑)

OpenStack Java SDK の現状

今回紹介する OpenStack Java SDK は、スペインの Luis Gervaso (@woorea)さんが開発している Java library です。
上記の OpenStack Project の SDK 一覧のページからもリンクされています。(以下 woorea 版と呼びます)

実は、各種クラウドサービス用のAPIの Java wrapper として著名な ASF の jclouds にも OpenStack API のサポートコードが含まれています。プロジェクトとしては jclouds のほうが勢いがあるようですね。

私が jclouds ではなく woorea 版を使っている理由は、当初は単純に OpenStack Project の Wiki からリンクされているから…というだけだったのですが、OpenStack Summit in Hong Kong 参加したJOSUG中島怪鳥から「jclouds のほうが勢いがあり、注目を集めている」という話を聞いた後、少し比較を行いました。

その結果、以下のような理由により、jclouds に switch せずにそのまま woorea 版を使っています。

まず、諸々の事情により、こんな感じの要件がありました。

  • 要件
    • OpenStack Native API を叩きたい
      • SDK層でハイブリッドクラウド対応する必要はない
      • 最低限 Nova と keystone が使えればよい
      • 管理系APIも(...というか、「を」)使いたい
    • 依存ライブラリも含めて、できるだけ規模が小さくメンテナンスが楽なほうがよい
    • もちろん品質は高いに越したことはない

当時の判断の根拠としては「jclouds は各種クラウドAPIを wrapping したハイブリッドクラウド向けAPIなので、管理系とか使えない(だろう)し、拡張するにしても無理があるよね...しかも、jclouds って絶望的に規模が巨大だし...orz」と考えたのが switch しなかった理由です。…が、このエントリを書くために再度確認してみたところ、かなり誤解していた ことが発覚しました...orz

ちょっと見てみましょう。
以下にあるのが jclouds の OpenStack API 用コードのサンプルです。

http://jclouds.apache.org/documentation/quickstart/openstack/

眺めてみるとわかるのですが、

  import org.jclouds.openstack.nova.v2_0.NovaApi;
  import org.jclouds.openstack.nova.v2_0.NovaAsyncApi;
  import org.jclouds.openstack.nova.v2_0.domain.Server;
  import org.jclouds.openstack.nova.v2_0.features.ServerApi;

といった感じで、Nova 用のクラスを直接 import して使っています。
必要なら、Native APIを呼ぶべし…というポリシーのようですね。

しかも実は、woorea 版には基本的に admin 権限が必要な管理系APIは含まれていないのに対して、jclouds には、 ここにあるような感じ で、既に Admin API のサポートも入っており、例えば Live Migration のコードも含まれています(ようです)。

後述の通り、既にけっこうがんばって woorea 版の拡張をしてしまったのですが、うーん、やっぱり jclouds に switch したほうがいいのかなぁ…とか、つらつら思いはじめた今日このごろです...orz

Woorea 版 OpenStack Java SDK の概要

さて、気を取り直して行きましょう。

Woorea 版 OpenStack の構造

woorea 版の OpenStack API Java SDK は以下のようなつくりになっています。

  • JSON/Java Object Mapping
  • REST通信
    • Jersey を使っています。
  • directory / Class 構造
    • 例えば nova であれば、大きくJSONメッセージを Java Object に mapping する nova-model と、API 処理本体の nova-client の2つに分かれています。

    • ディレクトリ構造は以下のような感じです。それぞれの下に src があります。

      • 全体の共通部分として openstack-client があり、REST通信処理部分は openstack-connectors 以下にあります。

      .
      |-- openstack-client (共通部)
      |-- openstack-client-connectors (REST通信処理部)
      | |-- jersey-connector (Jersery固有実装)
      | |-- jersey2-connector (Jersery2固有実装)
      | +-- resteasy-connector (上記付属品)
      |-- keystone-client (keystone API処理)
      |-- keystone-model (keystone API の Request/Responseを map する class)
      |-- nova-client (nova API処理)
      |-- nova-model (nova API の Request/Responseを map する class)
      |-- glance-client 以下同様
      |-- glance-model
      |-- quantum-client
      |-- quantum-model
      |-- swift-client
      |-- swift-model
      |-- ceilometer-client
      |-- ceilometer-model
      |-- openstack-examples (サンプル)
      |-- openstack-console (console client のサンプル...らしい)

コードを追ってみるとわかるのですが、特に Jackson を使って Request/Response を map した class を通信処理部分に受け渡す処理は、Java の Generics を使った巧妙な作りになっており、規模の増大をきれいに抑えています。
拡張もやりやすいです。

参考までに具体例をあげておくと、

https://github.com/woorea/openstack-java-sdk/blob/master/openstack-client/src/main/java/com/woorea/openstack/base/client/OpenStackRequest.java#L10

にある OpenStackRequest という class が共通 class で、これを例って、例えば 'nova boot'相当であれば、

https://github.com/woorea/openstack-java-sdk/blob/master/nova-client/src/main/java/com/woorea/openstack/nova/api/ServersResource.java#L83

といったように、nova-model から response を mapping する class を持ってきた上で、<R> のところ(Generics)にその型情報を渡した上で、extend して使っていることが分かります。
こうすると、あとは openstack-client 以下の class がやってくれるようになっています。

使い方

具体的なコードは後述のサンプルを見てほしいのですが、基本的に以下のような流れで使います。

  1. Keystone class を作成する * auth_url はここで設定します。
  2. Keystone class を使って認証処理を行う * username, password, tenant はここで設定します。 * 結果の token と endpoint は Access class に返却されます
  3. 認証して得られた Endpoint を取り出す * KeystoneUtils class のfindEndpointURL() method を使って取り出します。
  4. 認証して得られた token をとりだす * Access class の getToken() method を使います。
  5. Nova 等のコンポーネントごとの client class を作成する * 上で取り出した Endpoint は constructor に渡します。
  6. Nova 等のコンポーネントごとの client class に token を設定する * Nova class の場合は token() method を使います。
  7. client class に実装されている各種APIに対応する method を呼び出す * 例としては、Nova class の servers().show(SERVER_UUID).execute() など * レスポンスは、対応する Java object に格納されて返却される
  8. 返却値を使って自分の処理をする

簡単ですね。:)

woorea 版 OpenStack Java SDK の課題

さて、当初は 'nova list' や 'nova show' 相当のごく基本的なAPIを呼んでみたあと、どうしても必要だった Nova の管理系APIを追加実装して動かし、満足していたのですが、だんだんいろいろ試してみたくなりました。

余談ですが、もちろん自分で追加実装した分も呼び出して結合テストまで行う必要があります。そのために作りはじめたのが、後述の woorea 版 OpenStack Java SDKをたたくクライアントプログラムです。

これを使って、各種APIを叩いてほげっていった結果、 いくつか課題が見えてきました。
こんな感じです。

  1. 管理系のAPIサポートがない (これは既知の話)

  2. Neutron(Quantum)はL2しか動かない

  3. heat のコードがない

  4. cinder のコードがない

  5. swift 系のコードが動かない

  6. ceilometer のコードが動かない

  7. Nova の(ごく)一部のAPIが返す double quoted な JSON object を parse できない 等

  8. testコードがない(!?)

  9. は既出ですし、2. ~ 4. も必要なら作ればよい(というか、実際作った)話なのですが、実は 5. と 6. が少しやっかいな問題でした。

一言でいえば、「Java と JSON の相性の悪さ」に起因する問題です。 もう少し具体的に説明すると、JSON では、RFC の 2.節"JSON Grammar" の冒頭部に記述があるように、

 2. JSON Grammer
 
 A JSON text is a sequence of tokens.  The set of tokens includes six
 structural characters, strings, numbers, and three literal names.
 
 A JSON text is a serialized object or array.

     JSON-text = object / array

規格上は名前なしの array も top level element として許されています。つまり、'[' で始まる JSON response もありうるということです。しかし、woorea 版 Java SDK が使っている Jackson では、これを単純には扱うことができません。

もちろん、動的に型情報を取り出すことによって処理することはできるのですが、前述の通り woorea 版 Java SDK は、Java Generics に深く依存する作りになっている関係上、Generics に関連する Java の仕様である Type Erasure のために、この方法も使えないのでした。

Swift と Ceilometer はこのタイプに該当するレスポンスを返すため、upstream 版ではうまく処理できなかった…ということになります。

この課題を(とりあえず)どのように解決したのかは後述します。

Woorea 版 OpenStack Java SDK の拡張

まずは、どんな拡張を行ったのかを紹介します。

拡張版の開発ブランチは

https://github.com/thatsdone/openstack-java-sdk/tree/dev

にあります。

拡張した管理系API

これですべてはありませんが、以下のような感じです。

  • Nova
    • Server(仮想マシン) 系API
      • nova live-migration 相当を追加
    • Host 系API
      • 実装は存在したが呼び出せなかったので、使えるようにした
    • Hypervisor 系API
      • nova hypervisor-list/show/stats/servers 相当を新規追加
    • Aggregate 系API
      • nova aggregate-list/details/create/delete/add-host/remove-host/update/set-metadata を新規追加
    • AvailabilityZone系API
      • nova availability-zone-list 相当を使えるようにした
  • Swift
    • 後述の方法により既存コードを動作可能にした
  • Ceilometer
    • 後述の方法により既存コードを動作可能にした
  • Cinder
    • 新規追加
  • Heat
    • 新規追加
  • Neutron/Quantum
    • 管理構造のバグフィックスをいくつか
    • L3/Router 系APIまで動くようにしたほか、neutron agent-list 相当を追加

Nova の admin APIをいくつか追加した後は、ざっくばらんにAPIを叩いてみて ad hoc に 動かないところを直している…という実態が分かると思います(笑)

また、バグフィックスを中心に upstream に pull request を出し、取り込んでもらっています。(取り込まれたのは全体からするとごく一部ではありますが...)

Java と JSON の相性の悪さをどう解決するか?

さて、前述の課題をどう解決したのかも簡単に説明しておきます。

現状の woorea 版の実装では、

  1. JSON response を Java object に deserialize する時にエラーになる
  2. 素直に JSON の Array の mapping を実装したつもりでもコンパイルできない

のいずれかの症状になります。

後者は、swift の Account 情報を Account class だとして、OpenStackRequest class を extend して使う時に List<Account> 等と定義して渡したいのですが、肝心なListの中身の型情報が Generics を通すと消えてしまう…ということです。

あれこれ試行錯誤の末、結局

  • 前述の Java の Type Erasure の仕様のため、Generics で渡された型情報はどうしても compile 時に消去されてしまう
  • Jackson で用意されている方法では、実行時に型情報を取り出すことしかできない

ため、(私の Java スキルでは)きれいな解決策を見つけ出すのは無理だとあきらめて

  • woorea 版で request/response 処理用に用意されている共通 class (OpenStackReqeust)を直接使わず(=extendせず)、wrapper をかませる
  • wrapper の中から呼び出す openstack-client では、いったん String class へ de-serialize させる
  • API利用者に返却する前に、wrapper の中でもう一度 de-serialize する

という方法で解決しました。(つまり、2段de-serialize。キタナイですが...orz)

余談ですが、課題に挙げた7番も同じような方法(2段de-serialize)で解決しています。

OpenStack Java SDK クライアントプログラムの例

最後に woorea版 OpenStack Java SDK をたたくサンプルプログラムです。
ざっくり、AWS 用 CLI の aws コマンドみたいな仕様にしました(笑)

https://github.com/thatsdone/openstack-java-cli/

にあります。

使い方は、この実装が依存する拡張版の OpenStack Java SDK のインストールから含めて

https://github.com/thatsdone/openstack-java-cli/blob/master/README.md

に書いておきましたので、ご興味のある方はご参照ください。

そもそもの目的としては、Java のロジックから各種APIを発行し、JSONの response に基づいていろいろ処理したいからこのSDKを使うわけですが、ここではとりあえずAPIの呼び出し結果がわかればそれでよかったので、PrettyPrint しているだけ、つまり、A → B → A と元に戻しているだけだという...(笑)

例えばこんな感じです。

 $ /home/thatsdone/openstack-java-cli/bin/jopst nova hypervisor-stats
 {
   "count" : 2,
   "vcpus" : 4,
   "vcpus_used" : 0,
   "memory_mb" : 3754,
   "memory_mb_used" : 1024,
   "local_gb" : 28,
   "local_gb_used" : 0,
   "disk_available_least" : 23,
   "free_ram_mb" : 2730,
   "free_disk_gb" : 28,
   "current_workload" : 0,
   "running_vms" : 0
 }

nova hypervisor-stats という、admin 権限が必要なコマンド相当の REST response がとれているのがわかります。

jq 等と組み合わせて使うと、コマンドラインでけっこう便利に使えるのですが、それなら curl 使えば?というツッコミはなしと言うことで。(笑)

まとめ

  • OpenStack API を Java からつかう SDK について紹介しました。
    • OpenStrack API 用の Java SDK には、広く知られているだけで woorea 版と jclouds 版の2種類あります。これらのざっくりした比較にも触れました。
  • woorea 版Java SDKの課題について説明し、解決方法の1つを紹介しました。
  • woorea 版Java SDKを呼び出すクライアントプログラムを紹介しました。

Merry Christmas, and Happy (おぷ☆すた) Hacking!

Cheers! :)