hadoopのソースコードをeclipseにインポートしてデバッグ起動する時のメモ(WEB Interface編)
hdfs管理画面(http://localhost:50070/dfshealth.jsp)にアクセスするとなんか文字化けしてエラーが出てる。
localhost:50070を表示しようとすると
12/07/18 09:43:28 WARN mortbay.log: /dfshealth.jsp org.apache.jasper.JasperException: JSPのクラスをコンパイルできません JSPファイル: /dfshealth.jsp の中の20行目でエラーが発生しました 生成されたサーブレットのエラーです: The method getFSImage() from the type FSNamesystem is not visible JSPファイル: /dfshealth.jsp の中の20行目でエラーが発生しました 生成されたサーブレットのエラーです: The method getRemovedStorageDirs() from the type FSImage is not visible
明日以降調べる
hadoopのソースコードをeclipseにインポートしてデバッグ起動する時のメモ(DataNode編)
NameNode編に続き今回はDataNodeを起動する
org.apache.hadoop.hdfs.server.datanode.DataNodeのmainメソッドを起動する。
素直に起動した。
おわり
しかしログ出力の設定をしていなかったことに気づいてlog4j.propertiesを配置して、conf以下をビルドパスに加える
そしてもう一回起動してみるとエラーが出てた、、、
ERROR conf.Configuration: Failed to set setXIncludeAware(true) for parser org.apache.xerces.jaxp.DocumentBuilderFactoryImpl@114a0b74:java.lang.UnsupportedOperationException: setXIncludeAware is not supported on this JAXP implementation or earlier: class org.apache.xerces.jaxp.DocumentBuilderFactoryImpl java.lang.UnsupportedOperationException: setXIncludeAware is not supported on this JAXP implementation or earlier: class org.apache.xerces.jaxp.DocumentBuilderFactoryImpl at javax.xml.parsers.DocumentBuilderFactory.setXIncludeAware(DocumentBuilderFactory.java:589) at org.apache.hadoop.conf.Configuration.loadResource(Configuration.java:1143) at org.apache.hadoop.conf.Configuration.loadResources(Configuration.java:1119) at org.apache.hadoop.conf.Configuration.getProps(Configuration.java:1063) at org.apache.hadoop.conf.Configuration.set(Configuration.java:439) at org.apache.hadoop.hdfs.server.namenode.NameNode.setStartupOption(NameNode.java:1250) at org.apache.hadoop.hdfs.server.namenode.NameNode.createNameNode(NameNode.java:1267) at org.apache.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1288)
調べてみると
org.apache.hadoop.conf.Configuration:loadResources()で以下のようなコードがある
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); //ignore all comments inside the xml file docBuilderFactory.setIgnoringComments(true); //allow includes in the xml file docBuilderFactory.setNamespaceAware(true); try { docBuilderFactory.setXIncludeAware(true); } catch (UnsupportedOperationException e) { LOG.error("Failed to set setXIncludeAware(true) for parser " + docBuilderFactory + ":" + e, e); }
このsetXIncludeAware(true)のメソッドでDocumentBuilderFactoryの実装ライブラリがXML Inclusionsをサポートしているかチェックしている。
hadoop-1.0.3のivyに設定されているxercesはバージョンが1.4.4なので、その実装だとsetXIncludeAware(true)はExcepitonを返すようになっていた。
とりあえず新しいxercesのバージョンではXML Inclusionsをサポートしているようなのでバージョンを上げて対応する
ivy.xmlを修正
<dependency org="xerces" name="xerces" rev="${xerces.version}" conf="jdiff->default"> </dependency>
↓
<dependency org="xerces" name="xercesImpl" rev="${xerces.version}" conf="jdiff->default"> </dependency>
libraries.propertiesのファイルを修正してバージョンを上げる
xerces.version=1.4.4 ↓ xerces.version=2.10.0
これで解決
hadoopのソースコードをeclipseにインポートしてデバッグ起動する時のメモ(NameNode編)
環境は
Mac OS10.7.4
Eclipse Version: Indigo Service Release 2
eclispe ivy pluginのインストール
http://www.atmarkit.co.jp/fjava/rensai4/devtool22/devtool22_1.html
を参考にivyのpluginをeclipseにインストール
hadoopのsvnリポジトリからcheckoutする
http://svn.apache.org/repos/asf/hadoop/common/trunk
直下をcheckoutする
※このsvnディレクトリをrootディレクトリとした階層プロジェクトになっている。(pom.xmlのparentタグを利用している)
依存ライブラリの解決
Ivyによるライブラリダウンロード
svnからのcheckoutしたプロジェクトだとivy.xmlとかが見つからなかったので、tar.gzでソースコードダウンロードしてeclipseにインポートする方法にする。
http://shuyo.wordpress.com/2011/03/08/hadoop-development-environment-with-eclipse/
を参考に、新規javaプロジェクトをhadoop-1.0.3という名前で作成してhadoop-1.0.3.tar.gzをインポートする。
上記サイトでは依存ライブラリは手動でlibに突っ込めみたいなこと書いてあるけど、ivyで解決できるっぽい。
- hadoop-1.0.3/hadoop-1.0.3/ivy.xmlを右クリック
- add ivy Libraryを選択
- 出てきた設定画面でsettingsタブでProperty filesに$hadoop-1.0.3/hadoop-1.0.3/ivy/libraries.propertiesを追加
で、Finishで実行すると依存ライブラリを全部ダウンロードしてくれた
coreモジュールのビルド設定
org.apache.hadoop.fs.kfsのコンパイルエラーに対処する
coreプロジェクトのorg.apache.hadoop.fs.kfsあたりでコンパイルエラーが出ているので調べてみると
どうやらKosmos File SystemというFSを利用する際のライブラリが足りてないみたいだ。
ivy.xmlを見てみると
<!-- This is not in the repository <dependency org="org.kosmix" name="" rev="${kfs.version}" conf="kfs->default"/>-->
となっていたので、ivyでは取ってこれてないみたいなので、直接ダウンロードしてくる必要がある。
http://code.google.com/p/kosmosfs/
からtar.gzを落としてきて新規に作成したプロジェクトにインポート。
kfs-0.5/src/javaにビルドパスを通してkfs-0.5のビルドはOK。
hadoop-1.0.3側のプロジェクト参照を追加
これでorg.apache.hadoop.fs.kfsあたりのコンパイルエラーは解消
org.apache.hadoop.security.SecurityUtilのコンパイルエラーに対処する
import sun.net.dns.ResolverConfiguration;
import sun.net.util.IPAddressUtil;
が解決できないコンパイルエラーで、以下のような文言が出ている
Access restriction: The type ResolverConfiguration is not accessible due to restriction on required library /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/lib/rt.jar
http://blog.bitmeister.jp/?p=1486
を見つけたので、「ほほーこんなんあるのかー」と思いつつ、sun/net/**をAccessibleにして登録したらコンパイルエラーが消えた。
色いろあるんもんだ。
hdfsモジュールのビルド設定
NameNode起動
src/core/core-default.xmlに以下の値をセットする
<property> <name>fs.default.name</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/Users/${user.name}/development/hadoop-data</value> <description>A base for other temporary directories.</description> </property>
org.apache.hadoop.hdfs.server.namenode.NameNodeにメインメソッドがあるので起動する
NameNodeデータディレクトリをフォーマットする
そのまま起動すると
java.io.IOException: NameNode is not formatted.
が出るのでNameNodeのデータファイルを格納するディレクトリのフォーマットが必要
org.apache.hadoop.hdfs.server.namenode.NameNodeの起動オプションに-formatをつけるとNameNode.main()からNameNode.format()が呼ばれて
フォーマット処理がはしる
データディレクトリのフォーマットに関係しそうなクラスはこんな感じ
NameNode.java
|
♢
FSNamesystem implements FSConstants, FSNamesystemMBean
|
♢
FSDirectory implements FSConstants, Closeable
|
♢
FSImage
|
∇
Storage
java.io.IOException: webapps not found in CLASSPATHが出る問題
NameNodeのコンストラクタで初期化処理をする際にhttpServerを起動する処理中に
org.apache.hadoop.http.HttpServer:getWebAppsPath()
の中で
URL url = getClass().getClassLoader().getResource("webapps");
でIOExceptionが出ている。
hadoop-1.0.3/src/webappsをそのままソースディレクトリとして指定するとwebappsのなかのディレクトリが展開されてしまっていて、webappsのリソースが取得できない。
調べても回避方法がよくわからないので
hadoop-1.0.3/src/webapps/の下にもう一つwebappsディレクトリを作って、webapps以下を全部webapps/webapps/以下に移動させた。
再ビルドするとhadoop-1.0.3/bin以下にちゃんとwebappsディレクトリが作成されていることを確認
再度NameNodeのmainメソッドから実行してみるとNameNodeの起動に成功しました
BloomFilterを使ってみる
http://dev.ariel-networks.com/column/tech/boom_filter/
http://www.perl.com/pub/2004/04/08/bloom_filters.html
hadoopにBloomFilterの実装があったのでそのクラスを使ってみる。
その対象のデータに対して検索したい場合の検索インデックスとしてDBなどに保存するために、BloomFilterを使って生成したbit列をシリアライズする方法も試した。
bit列をもとに同じBloomFilterを復元することもできた。
なんかに使えそう。
/** * */ package jp.co.cyberagent.partner.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import junit.framework.Assert; import org.apache.hadoop.util.bloom.BloomFilter; import org.apache.hadoop.util.bloom.Key; import org.apache.hadoop.util.hash.Hash; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author watanabe_yusaku * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration() public class BloomFilterTest { @Configuration static class Config{ } @Test public void test() { //Filterテスト BloomFilter b = new BloomFilter(128, 128, Hash.MURMUR_HASH); b.add(new Key(new String("yuhsaku").getBytes())); b.add(new Key(new String("hiroki").getBytes())); b.add(new Key(new String("yumi").getBytes())); b.add(new Key(new String("kunitaka").getBytes())); b.add(new Key(new String("mitsuko").getBytes())); b.add(new Key(new String("tsuyoshi").getBytes())); Assert.assertTrue(b.membershipTest(new Key("yuhsaku".getBytes()))); Assert.assertFalse(b.membershipTest(new Key("hoge".getBytes()))); //Filterのbit列をシリアライズしてから再度復元してちゃんとFilterできるか確認 BloomFilter rebuildBloomFilter = new BloomFilter(128, 128, Hash.MURMUR_HASH); ByteArrayOutputStream bytearrayOs = new ByteArrayOutputStream(); DataOutputStream s = new DataOutputStream(bytearrayOs); byte[] bytes; try { b.write(s); System.out.println("size:" + s.size()); bytes = new byte[s.size()]; bytes = bytearrayOs.toByteArray(); rebuildBloomFilter.readFields(new DataInputStream(new ByteArrayInputStream(bytes))); }catch(IOException ioe) { Assert.fail(); } Assert.assertTrue(b.membershipTest(new Key("yuhsaku".getBytes()))); Assert.assertFalse(b.membershipTest(new Key("hoge".getBytes()))); } }
HBaseの概要理解のメモ
Apache HttpComponents(旧httpClient)でssl通信時のエラー
アプリケーションからhttps通信を使って他のサーバと連携することはよくあると思います。
例えばfacebookやgoogleのOAuth認証のOPとやりとりする場合とか。
私は開発時はMac上で行なっているがその時点ではjava7はOpenJDKしかなかったので(現在はMac版のoracle jdk7が出てますね)、ほとんどの開発をOpenJDK上で行なっていたんですが、なぜかHttpComponentsを使ってhttpsの通信を行うと下記エラーが出る。
Exception in thread "main" java.lang.RuntimeException: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at Main.execute(Main.java:62) at Main.main(Main.java:19) Caused by: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:397) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:150) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:575) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732) at Main.execute(Main.java:54) ... 1 more
サンプルコードは以下
public class Main { public static void main(String[] args) throws Exception{ Main m = new Main(); m.execute(args); } public void execute(String[] args) throws Exception{ System.out.println(System.getProperty("java.version")); HttpClient httpClient = new DefaultHttpClient(); //ちょうどmixi developer Centerにサーバ証明書についてのFAQがあったので使わしてもらう //http://developer.mixi.co.jp/openid/faq/ HttpGet get = new HttpGet("https://mixi.jp/"); HttpResponse res = httpClient.execute(get); if(res.getStatusLine().getStatusCode() >= 300) { System.out.println(res.getStatusLine().getStatusCode()); } System.out.println(EntityUtils.toString(res.getEntity())); } }
httpComponentsのバージョンは
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.1.3</version> </dependency>
を使っています
正直あまりsslについて知識がなかったので、とりあえずHttpClientでSSL通信(HttpClient3.0-rc3) にしたがってサーバ証明書を登録して事無きを得たのだが、どうにも気持ち悪い(いちいちこんな作業をしないとhttps通信できないのか?という気持ち悪さ)
ちなみにいちいち証明書の認証とかめんどくせぇという人は以下のように書くとオレオレ証明書などでも無視して全部受けて入れるようにできます。
try { TrustStrategy trustStrategy = new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }; X509HostnameVerifier hostnameVerifier = new AllowAllHostnameVerifier(); SSLSocketFactory sslSf = new SSLSocketFactory(trustStrategy, hostnameVerifier); SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("https", 8443, sslSf)); schemeRegistry.register(new Scheme("https", 443, sslSf)); ClientConnectionManager connection = new ThreadSafeClientConnManager(schemeRegistry); HttpClient httpClient = new DefaultHttpClient(connection); }catch(UnrecoverableKeyException uke) { //TODO }catch(KeyStoreException kse) { //TODO }catch(KeyManagementException kme) { //TODO }catch(NoSuchAlgorithmException nsae) { //TODO }
しかし今回はライブラリを開発しているので勝手に証明書を無視するようなコードを入れ込むわけにはいきません。
というわけで、改めてsslの仕組みを調べてjavaのssl通信の仕組みについてお勉強。
どうやらデフォルトでcacertsというファイルにいわゆるCAルート証明書がインストールされていることになっているようだ。
なので、httpComponentsを使ったアプリケーションのjavaランタイムのcacertsにCAルート証明書が入っていないと、サーバ証明書のクライアント認証が通せないのでエラーになるということみたいです。
実際にMac標準にインストールされているJDKのcacertsを確認するとかなりの量のルート証明書がインポートされている
$ keytool -v -list -keystore /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/security/cacerts | more キーストアのパスワードを入力してください: #←デフォルトパスワードはchangeitなので、それを入力 キーストアのタイプ: JKS キーストア・プロバイダ: SUN キーストアには183エントリが含まれます 別名: keychainrootca-99 作成日: 2012/03/23 エントリ・タイプ: trustedCertEntry 所有者: CN=TWCA Root Certification Authority, OU=Root CA, O=TAIWAN-CA, C=TW 発行者: CN=TWCA Root Certification Authority, OU=Root CA, O=TAIWAN-CA, C=TW シリアル番号: 1 有効期間の開始日: Thu Aug 28 16:24:33 JST 2008終了日: Wed Jan 01 00:59:59 JST 2031 証明書のフィンガプリント: MD5: AA:08:8F:F6:F9:7B:B7:F2:B1:A7:1E:9B:EA:EA:BD:79 SHA1: CF:9E:87:6D:D3:EB:FC:42:26:97:A3:B5:A3:7A:A0:76:A9:06:23:48 SHA256: BF:D8:8F:E1:10:1C:41:AE:3E:80:1B:F8:BE:56:35:0E:E9:BA:D1:A6:B9:BD:51:5E:DC:5C:6D:5B:87:11:AC:44 署名アルゴリズム名: SHA1withRSA バージョン: 3 拡張: #1: ObjectId: 2.5.29.14 Criticality=false ・ ・ ・以下略
openjdkのほうは空っぽ
$ keytool -v -list -keystore /Library/Java/JavaVirtualMachines/1.7.0u4.jdk/Contents/Home/jre/lib/security/cacerts キーストアのパスワードを入力してください: キーストアのタイプ: JKS キーストア・プロバイダ: SUN キーストアには0エントリが含まれます
これが原因でサーバ証明書の認証ができないためエラーになったみたい。
試しに同じコードでMacの標準jdkにビルドパスを変更したら問題なく通信できました。
openjdkを使う場合はCAルート証明書を自前でインポートするとかしないといけないのだろうか。
そのへんまでは調べきれてないけど、とりあえず原因がわかったのですっきりした。
必要になったらまた追記します。