Scalaでfindify/s3mockによるテストを行おうとしてエラーにハマった結果max8github/s3mockでカバーした話
はじめに
ScalaでS3を利用した処理のテストを行いたい場合に、findify/s3mockを使う方法をよく見かけます。stackoverflowでもコード例を見かけるために非常に手間が省けるのですが、実際にやってみると環境によってはかなり頭を抱えるトラップがあります。
- Bucketは作れる
- putObjectはエラーになる
エラーの原因と、対処方法についてまとめました。
エラーの原因
なにがエラーで何がエラーじゃないのか、loggerを使って詳細を確認しました。その結果、
- ファイル追加は正常に行われる
- 追加した後の処理がエラーになる
という厄介な状態になっていることがわかりました。つまり、ファイルは置かれても例外によりassert
がエラーになります。
原因としては、PutObject実行時にモックサーバがjdk.internal.ref.Cleaner
による500 Internal Server Errorを返すためでした。
DEBUG com.amazonaws.request - Received error response: com.amazonaws.services.s3.model.AmazonS3Exception: java.lang.IllegalAccessException: class org.iq80.leveldb.util.ByteBufferSupport cannot access class jdk.internal.ref.Cleaner (in module java.base) because module java.base does not export jdk.internal.ref to unnamed module @32a5e0f3 (Service: Amazon S3; Status Code: 500; Error Code: InternalError; Request ID: null; S3 Extended Request ID: null; Proxy: null), S3 Extended Request ID: null
例外を拾ってスルーする手もあるかもしれませんが、これ以外の例外に遭遇した時に詰みそうです。
Javaでの対処方法
結構致命的ですが、Javaを使っている場合には割と簡潔な対応策があるようでした。jvm-opts
に以下の指定をいれましょう。
"--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED"
build.gradleであれば以下の指定になります。
def jvmArgsAddOpens = [ "--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED" ]
が、今回はScalaです。これが使えない。
Scalaでの対処方法
Issueでの調査結果によると、オプションによる調整手段は使えないようで、対策が取られているDockerイメージへの置き換えとなります。
The above happens when a putObject is issued. In FileProvider, the file gets saved, but not the metadata. The call metadataStore.put(bucket, key, objectMetadata) will cause an exception that will leave a file locked (a LOCK file in the metadata directory for the bucket). The offending code is in library leveldb, class DbLock. In its constructor, tryLock throws the exception above leaving metadata in an inconsistent state, thus compromising any successive getObject call (getObject will fail as it will find a lock).
All of that only happens with java 9, not with java 8. See https://bugs.openjdk.java.net/browse/JDK-8148117.
So for all that, the easy workaround is to either use java 8, or to use S3Mock's in memory provider (InMemoryProvider) instead of FileProvider.
Going to create a docker image tagging it with java 9 and "inmem". See https://hub.docker.com/r/max8github/s3mock/tags
$ docker run -p 8001:8001 -v /path/to/fixtures/:/tmp/s3mock/ -e "S3MOCK_DATA_DIR=/tmp/s3mock" max8github/s3mock:0.2.4.1-8-jdk-slim
なお、Issue内に記載されているタグは古いため機能しません。
まとめ
s3mockにはfindify/s3mockとadobe/S3Mockの2つが存在します。adobe版は基本的にJavaでの利用が想定されているようで、JUnit4、JUnit5、TestNGが中心となります。そのためfindify版を選択しましたが、根が深いエラーに遭遇するという有様でした。
環境によって遭遇するかどうかも変わるかもしれませんが、同様のエラーに遭遇した場合に解消の一端となれば幸いです。