Hudsonを利用してファイルI/Fを実現したい(3)

昨日(id:sikakura:20110224)までで、Apache Commons HttpClientを使用し、
HudosnのリモートアクセスAPIを呼び出すところまで確認できました。

必要となる機能は大きく分けて2つ考えていて、

  1. Hudsonジョブの起動と監視
  2. 指定URLのファイルダウンロードと保存

でファイルI/Fが実現できるかなと思います。

Hudsonジョブの起動と監視から考えていきます。
ジョブの起動は、単にURLにアクセスすれば可能ですので、昨日のサンプルを実行すれば出来てしまいます。
ジョブの監視ですが、ジョブの状態をリクエストし、結果を見ます。
ジョブの状態を見るためには、ジョブの番号の取得が必要でそれは以下のURLにアクセスすれば可能ということが分かりました。


http://[ホスト名(or IPアドレス)]/hudson/job/[ジョブ名]/api/xml
上のHTTPリクエストの結果としてXMLが返却されますが、その中のを見れば次のジョブの番号が判明します。
昨日のサンプルを少し変更して、initialize(url)メソッドにまとめました。
直接書いているもの(HudsonのIPアドレスプロトコル、ポートやユーザ名、パスワード)を
HUDSON_ADDR,HUDSON_PROTOCOL,HUDSON_PORT,HUDSON_USER,HUDSON_PASS
などに変換して、後から指定できるようにしています。
※下の例では、本来のサンプルに実装してあるPreemptiveAuthクラスとPersistentDigestクラスが省略されています!
 実際の実行では必要になります。

    void initialize(String url) {
        HttpParams params = new BasicHttpParams();
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme(HUDSON_PROTCOL, PlainSocketFactory.getSocketFactory(), HUDSON_PORT));
        ClientConnectionManager connMrg = new ThreadSafeClientConnManager(params, schemeRegistry);
        httpclient = new DefaultHttpClient(connMrg, params);

        httpclient.getCredentialsProvider().setCredentials(
            new AuthScope(HUDSON_ADDR, HUDSON_PORT),
            new UsernamePasswordCredentials(HUDSON_USER, HUDSON_PASS));

        DigestScheme digestAuth = new DigestScheme();
        digestAuth.overrideParamter("realm", "trac");
        digestAuth.overrideParamter("nonce", "whatever");
        localcontext.setAttribute("preemptive-auth", digestAuth);

        httpclient.addRequestInterceptor(new PreemptiveAuth(), 0);
        httpclient.addResponseInterceptor(new PersistentDigest());

        targetHost = new HttpHost(HUDSON_ADDR, HUDSON_PORT, HUDSON_PROTCOL);
        httppost = new HttpPost(url);
    }

以下が特定のkyeを指定すると、レスポンスのXMLを検索して値を返してくるようなサンプルです。
上でのinitialize(url)メソッドを使用しています。
urlには、"http://192.168.0.1/hudson/job/IFJOB/api/xml"のような文字列を想定しています。
kyeには、"nextBuildNumber"を想定しています。

    String request(String url, String key) {
        initialize(url);
        String returnStr = null;
        InputStream is = null;
        try {
            for (int i = 0; i < 2; i++) {
                HttpResponse response =
                    httpclient.execute(targetHost, httppost, localcontext);
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    is = response.getEntity().getContent();
                }
            }
            Document dom = new SAXReader().read(is);
            for (Element job : (List<Element>) dom.getRootElement().elements(key)) {
                returnStr = job.getStringValue();
            }
            is.close();
        } catch (ClientProtocolException e) {
        } catch (IOException ioe) {
        } catch (DocumentException de) {
        } finally {
            httpclient.getConnectionManager().shutdown();
        }
        return returnStr;
    }

次に、ジョブの実行を行います。
urlには、"http://192.168.0.1/hudson/job/IFJOB/build"のような文字列を想定しています。

    void execute(String url) {
        initialize(url);
        try {
            for (int i = 0; i < 2; i++) {
                HttpResponse response =
                    httpclient.execute(targetHost, httppost, localcontext);
                HttpEntity entity = response.getEntity();
                if (entity != null)
                    entity.consumeContent();
            }
        } catch (ClientProtocolException e) {
        } catch (IOException ioe) {
        } finally {
            httpclient.getConnectionManager().shutdown();
        }
    }

最後に、得られたビルドナンバーを元にジョブのステータスを要求するようなURLを生成し、実行します。
今度はkeyとして"result"を指定します。


ジョブの実行中は、レスポンスXMLにはという要素がありません。
よって、ジョブの実行中にrequestメソッドを実行すると返却値はnullになります。
ジョブが終了するとという要素が含まれたXMLを返却する仕様のようですので、
requestメソッドの結果がnull以外になるまで繰り返して実行すればジョブの監視ができるようです。


上のメソッドと合わせて、以下のような感じになります。
動作内容からはsendというメソッド名はふさわしくないのですが、I/Fのsend(送信)とrecieve(受信)に見立てて命名してみました。
HUDSON_JOBURL:"http://192.168.0.1/hudson/job/"など
kickjobname:"IFJOB"など
POLLING_TIME:ポーリング実行回数
POLLING_MSEC:ポーリング時間間隔(msec)

    public final void send(String kickjobname) {
        // ビルドナンバーの取得
        String buildNum = request(HUDSON_JOBURL+ kickjobname+"/api/xml", "nextBuildNumber");
        // ジョブの実行
        execute(HUDSON_JOBURL+kickjobname+"/build");
        // ジョブの監視(POLLING_MSEC毎に実行)
        for (int i = 0; i < POLLING_TIME; i++) {
            try {
                Thread.sleep(POLLING_MSEC);
            } catch (InterruptedException e) {
            }
            String resultStr = request(HUDSON_JOBURL+kickjobname+ "/"+buildNum+ "/api/xml", "result");
            if (resultStr == null) {
                System.out.println("Now Running...");
            } else {
                System.out.println(resultStr);
                if (resultStr.compareToIgnoreCase("SUCCESS") == 0) {
                    System.exit(0);
                } else {
                    System.exit(1);
                }
            }
        }
        System.err.println("タイムアウトしました。ポーリング間隔:"+POLLING_MSEC+" ポーリング実行回数:"+ POLLING_TIME);
        System.exit(-1);
    }


実際に動かしてみましたが、Hudsonジョブの起動と監視は上のようなサンプルで行けそうです。
あとはHudsonジョブから実行されるダウンロード処理ができれば、なんとかなりそうです。

今日はここまで。