TL;DR
- 自動インストールでインストールされるJDKのアーキテクチャは、各Nodeを登録するために起動するslave.jarのJREのアーキテクチャだよ
- Jenkinsのソースコードは構造のバランスがいい
- Jenkinsは週次リリースできるソフトウェアのアーキテクチャを学ぶ題材として最適
きっかけ
Jenkinsの便利な機能に、JDKの自動インストールがある。プロジェクトに利用するJDKを設定すれば、実行するノードに自動でJDKをインストールしてくれる、という素晴らしい機能だ。WindowsやLinuxだと32bit、64bitのJDKが配布されているので、どうやってインストールするJDKを判定してるんだろう、と思ってコードを読み始めた。
ゴール
- 自動インストールを実装しているクラスを見つけ、どのように実装されているか理解すること
実装クラスを見つける
実装を見つけるときは、ログを出力して実際にその操作をすると、どのクラスで実行されているかすぐにわかる。ただ、今回はテスト用のJenkinsを立ち上げるのは面倒だったので、ソースコードリポジトリの中を見てみることにした。
JenkinsのリポジトリはGitHubで公開されている。リポジトリが公開されているので、リポジトリの中を手当たり次第パッケージをたどってパッケージから全体の構造を推測してみようと試みた。しかし、hudson時代のパッケージもあったりしてどこを見たらいいのかわからない。hudson時代のパッケージが残るのは、APIの互換性維持のためだと思うので、仕方ないことだとおもう。
あきらめて、JDKのインストールなので、JDK
でリポジトリを検索してみた。するとhudson.tools.JDKInstallerというそのものズバリが見つかった。
関連するクラスとか見ると、親クラスがToolInstaller
とかなので、このクラスだろう。
で、どんな実装だった?
public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException {
...
Platform p = Platform.of(node);
URL url = locate(log, p, CPU.of(node));
FilePath file = expectedLocation.child(p.bundleFileName);
file.copyFrom(url);
NodeのPlatformとCPUアーキテクチャを調べ、そのURLを取得し、ダウンロードする。では、このURLを計算するlocate()では何をやっているのだろう?
JDKFamilyList families = JDKList.all().get(JDKList.class).toList();
if (families.isEmpty())
throw new IOException("JDK data is empty.");
JDKRelease release = families.getRelease(id);
if (release==null)
throw new IOException("Unable to find JDK with ID="+id);
JDKFile primary=null,secondary=null;
for (JDKFile f : release.files) {
String vcap = f.name.toUpperCase(Locale.ENGLISH);
// JDK files have either 'windows', 'linux', or 'solaris' in its name, so that allows us to throw
// away unapplicable stuff right away
if(!platform.is(vcap))
continue;
switch (cpu.accept(vcap)) {
case PRIMARY: primary = f;break;
case SECONDARY: secondary=f;break;
case UNACCEPTABLE: break;
}
}
JDKListからJDKFileを取り出し、JDKFile毎にPlatformとCPUがOKとするかチェックしている。CPU#accept()
でPRIMARY
とSECONDARY
があるのは、64bitアーキテクチャのJREが動いているNodeであれば、32bitでもOKだからだろう。このJDKListはどうやって取得しているのか?
public static final class JDKList extends Downloadable
public JDKList() {
super(JDKInstaller.class);
}
public JDKFamilyList toList() throws IOException {
JSONObject d = getData();
if(d==null) return new JDKFamilyList();
return (JDKFamilyList)JSONObject.toBean(d,JDKFamilyList.class);
}
}
Downloadable
ってなんだろう?import文を見てみると、
import hudson.model.DownloadService.Downloadable;
となっているので、Downloadable
はDownloadService
の中に定義されたクラスのようだ。DownloadServiceを検索し、Downloadableの定義周辺を見てみる。
/**
* Represents a periodically updated JSON data file obtained from a remote URL.
*
* <p>
* This mechanism is one of the basis of the update center, which involves fetching
* up-to-date data file.
*
* @since 1.305
*/
public static class Downloadable implements ExtensionPoint {
...
/**
*
* @param url
* URL relative to {@link UpdateCenter#getDefaultBaseUrl()}.
* So if this string is "foo.json", the ultimate URL will be
* something like "http://updates.jenkins-ci.org/updates/foo.json"
*
* For security and privacy reasons, we don't allow the retrieval
* from random locations.
*/
public Downloadable(String id, String url, long interval) {
this.id = id;
this.url = url;
this.interval = interval;
}
public Downloadable() {
this.id = getClass().getName().replace('$','.');
this.url = this.id+".json";
this.interval = DEFAULT_INTERVAL;
}
...
public Downloadable(Class id) {
this(id.getName().replace('$','.'));
}
...
public String getUrl() {
return Jenkins.getInstance().getUpdateCenter().getDefaultBaseUrl()+"updates/"+url;
}
}
JavaDocにちゃんと書いてある。Downloadableのパラメータurl
はURL relative to {@link UpdateCenter#getDefaultBaseUrl()}.
と書かれている。urlはUpdateCenter#getDefaultBaseUrl()からの相対パス
です。もしfoo.json
という文字列であれば、アクセスするURLはhttp://updates.jenkins-ci.org/updates/foo.json
のようになる。
さっきのJDKList
のコンストラクタはthis(JDKInstaller.class)
なので、下記のURLからJSONを取得する。
JSONP形式のJSONが取得できるはずだ。
Q.UpdateCenter#getDefaultBaseUrl()ってほんとにhttp://updates.jenkins-ci.org/
なの?
そういう時はJenkinsのスクリプトコンソールを開いて試してみればいい。スクリプトコンソールはJenkinsの管理を開こう。Groovyはスクリプト環境はJavaのコードを解釈できる。UpdateCenterはJenkinsのインスタンスから取得する。下記のように書くといい。
JDKInstallerの中のドメインモデル
ところで、JDKInstallerの中でPlatform
やCPU
というクラスが定義されている。Jenkinsはクラスの定義がモデルと対応していて、感銘を受けた。JDKInstaller#JDKListの周辺クラスは下記のとおりだ。
個々のクラスが小さいとファイル管理が大変になる。しかし、ネストクラスとして定義することでファイル管理の大変さを解決しつつ、概念をうまくモデル化し、コードに落としこんでいる。
そういう意味でも大変勉強になった。