CLOVER🍀

That was when it all began.

AlluxioをS3ストレージとして使ってみる(curl/Java/Node.js)

少し間が空きましたが、AlluxioをS3ストレージとして使ってみたいと思います。

S3 Client - Docs | Alluxio Open Source

Alluxioは、すべてのS3の機能をサポートしているわけではないので、注意が必要です。

Features support

ドキュメントには、curlでアクセスするサンプルが記載されています。

REST API

こちらを参考に、ちょっと遊んでみましょう。

なお、書いている人はAWS S3を使ったことがありません。

環境

使ったAlluxioのバージョンは、1.7.0です。また、AlluxioのIPアドレスは「172.17.0.2」とし、ローカルモードでMasterもWorkerも単一のホストで
動作しているものとします。

REST APIでアクセス

curlを使って、REST APIにアクセスしてみます。使うにあたって、特にAccessKeyやSecretKeyの設定は不要です。

アクセスするURLは、「http://[IPアドレス]:[ポート(デフォルト39999)/api/v1/s3」となります。
39999ポートというのは、AlluxioのProxy Web Serviceのものになります。

Bucketの作成。

$ curl -i -XPUT http://172.17.0.2:39999/api/v1/s3/test-bucket
HTTP/1.1 200 OK
Date: Wed, 21 Mar 2018 14:41:55 GMT
Content-Length: 0
Server: Jetty(9.2.z-SNAPSHOT)

オブジェクトの登録。

$ curl -i -XPUT http://172.17.0.2:39999/api/v1/s3/test-bucket/test-object -d 'Hello Alluxio!!'
HTTP/1.1 200 OK
Date: Wed, 21 Mar 2018 14:42:23 GMT
ETag: "10d9c68bbf10a8b8cdef7f304d2f3055"
Content-Length: 0
Server: Jetty(9.2.z-SNAPSHOT)

オブジェクトの取得。

$ curl -i http://172.17.0.2:39999/api/v1/s3/test-bucket/test-object
HTTP/1.1 200 OK
Date: Wed, 21 Mar 2018 14:43:43 GMT
Last-Modified: Wed, 21 Mar 2018 14:42:24 GMT
Content-Type: application/xml
Content-Length: 15
Server: Jetty(9.2.z-SNAPSHOT)

Hello Alluxio!!

オブジェクトの削除。

$ curl -i -XDELETE http://172.17.0.2:39999/api/v1/s3/test-bucket/test-object
HTTP/1.1 204 No Content
Date: Wed, 21 Mar 2018 14:46:27 GMT
Server: Jetty(9.2.z-SNAPSHOT)

Bucketの削除。

$ curl -i -XDELETE http://172.17.0.2:39999/api/v1/s3/test-bucket
HTTP/1.1 204 No Content
Date: Wed, 21 Mar 2018 14:46:52 GMT
Server: Jetty(9.2.z-SNAPSHOT)

とりあえずは、こんなところでしょうか。

AWSクライアントライブラリでアクセス

続いて、AWSのクライアントライブラリを使ってアクセスしていこうと思います。対象には、Java(というかGroovy)とNode.jsを選択。
いやぁ、てこずりました。

最終的に、以下の点に注意しておけばなんとかアクセスできるようになりました。

  • AccessKeyとSecretKeyは、ダミーでいいので設定する
  • エンドポイントのURLをAlluxioのものに設定して、かつPath Styleでのアクセスとする
  • Bucket作成時のContent-Typeがapplication/octet-streamだとAlluxioが受け付けないため、Content-Typeの設定を外す(Node.jsは諦めた)
  • Content-MD5の検証に失敗するのでオフにする(Javaのみ)

では、いってみます。

Java/Groovy

環境。

$ groovy -v
Groovy Version: 2.4.14 JVM: 1.8.0_151 Vendor: Oracle Corporation OS: Linux

とりあえず、このあたりを参考にaws-sdk-s3を使ってみます。

Amazon S3 バケットの作成、一覧表示、削除 - AWS SDK for Java

AWS SDK for Java を使用したオブジェクトのアップロード - Amazon Simple Storage Service

AWS SDK for Java を使用したオブジェクトの取得 - Amazon Simple Storage Service

AWS 認証情報の使用 - AWS SDK for Java

できあがったプログラムは、こちら。
alluxio-s3-client-example.groovy

@Grab('com.amazonaws:aws-java-sdk-s3:1.11.298')
import com.amazonaws.ClientConfiguration
import com.amazonaws.auth.AWSStaticCredentialsProvider
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.services.s3.AmazonS3ClientBuilder
import com.amazonaws.services.s3.model.DeleteObjectRequest
import com.amazonaws.services.s3.model.GetObjectRequest
import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.PutObjectRequest
import com.amazonaws.client.builder.AwsClientBuilder

// Content-MD5とEtagの検証を飛ばす
System.setProperty('com.amazonaws.services.s3.disableGetObjectMD5Validation', 'false')
System.setProperty('com.amazonaws.services.s3.disablePutObjectMD5Validation', 'false')

// access-keyとsecret-access-keyはなんでもいい
def awsCreds = new BasicAWSCredentials('test-access-key-id', 'test-secret-access-key')
def s3Client =
  AmazonS3ClientBuilder
  .standard()
  .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
  // Alluxioのエンドポイントを設定
  .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration('http://172.17.0.2:39999/api/v1/s3', null))
  // Path Styleでのアクセスとする
  .withPathStyleAccessEnabled(true)
  .withChunkedEncodingDisabled(true)
  // Content-Typeがapplication/octet-streamだとBucketの作成で失敗するので切る
  .withClientConfiguration(new ClientConfiguration().withHeader('Content-Type', null))
  .build()

def bucketName = 'groovy-bucket'

if (s3Client.doesBucketExist(bucketName)) {
  println("alredy exists bucket[$bucketName]")
} else {
  s3Client.createBucket(bucketName)
  println("bucket[$bucketName] created")
}

def objectName = 'message'

if (s3Client.doesObjectExist(bucketName, objectName)) {
  // no-op
} else {
  // PUT object
  def message = 'Hello Groovy!!'

  def metadata = new ObjectMetadata()
  metadata.contentLength = message.getBytes('UTF-8').length

  def data = new ByteArrayInputStream(message.getBytes('UTF-8'))

  s3Client.putObject(new PutObjectRequest(bucketName, objectName, data, metadata))

  println("put object[$objectName], [$message]")
}

// GET object
def s3Object = s3Client.getObject(new GetObjectRequest(bucketName, objectName))
def content = s3Object.objectContent
def message = content.getText('UTF-8')

println("get object[$message]")


// DELETE object
s3Client.deleteObject(new DeleteObjectRequest(bucketName, objectName))
assert s3Client.doesObjectExist(bucketName, objectName) == false

だいたい先頭の方に書いていますが、このあたりが注意点そのままです。

// Content-MD5とEtagの検証を飛ばす
System.setProperty('com.amazonaws.services.s3.disableGetObjectMD5Validation', 'false')
System.setProperty('com.amazonaws.services.s3.disablePutObjectMD5Validation', 'false')

// access-keyとsecret-access-keyはなんでもいい
def awsCreds = new BasicAWSCredentials('test-access-key-id', 'test-secret-access-key')
def s3Client =
  AmazonS3ClientBuilder
  .standard()
  .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
  // Alluxioのエンドポイントを設定
  .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration('http://172.17.0.2:39999/api/v1/s3', null))
  // Path Styleでのアクセスとする
  .withPathStyleAccessEnabled(true)
  .withChunkedEncodingDisabled(true)
  // Content-Typeがapplication/octet-streamだとBucketの作成で失敗するので切る
  .withClientConfiguration(new ClientConfiguration().withHeader('Content-Type', null))
  .build()

Path Styleにしてあげないと、そもそもアクセスできません。また、Content-Typeをつぶしておかないと、Bucketが作れません。そして、オブジェクトを
登録する際にContent-MD5の検証にどうしても失敗するのでオフに。

なんなのでしょう…。

実行すると、こんな感じに。

## 1回目
$ groovy alluxio-s3-client-example.groovy 
bucket[groovy-bucket] created
put object[message], [Hello Groovy!!]
get object[Hello Groovy!!]

## 2回目(Bucket存在済み)
$ groovy alluxio-s3-client-example.groovy 
alredy exists bucket[groovy-bucket]
put object[message], [Hello Groovy!!]
get object[Hello Groovy!!]
Node.js

環境。

$ node -v
v9.8.0

$ npm -v
5.6.0

aws-sdkをインストール。

$ npm i --save aws-sdk

確認は、Jestで行うことにします。

$ npm i --save-dev jest

依存関係。

  "dependencies": {
    "aws-sdk": "^2.212.1"
  },
  "devDependencies": {
    "jest": "^22.4.2"
  }
  "scripts": {
    "test": "jest"
  },

なお、AWSJavaScriptクライアントライブラリでは、Bucketを作るのが厳しかった(Content-Typeの変更が苦しい)ので先に作っておくことにしました…。

$ curl -i -XPUT http://172.17.0.2:39999/api/v1/s3/nodejs-bucket

プログラムは、このあたりを参考に。

Node.js 内の AWS SDK for JavaScript | AWS

Class: AWS.S3 — AWS SDK for JavaScript

Class: AWS.Config — AWS SDK for JavaScript

できあがったコードは、こんな感じです。
test/alluxio-s3.test.js

const AWS = require("aws-sdk");

AWS.config.update({
    accessKeyId: "access-key-id",
    secretAccessKey: "secret-access-key",
    s3: { endpoint: "http://172.17.0.2:39999/api/v1/s3" },
    s3ForcePathStyle: true
});

const s3 = new AWS.S3();

const bucketName = "nodejs-bucket";
const objectName = "nodejs-object";
const message = "Hello Node.js!!";

test("put-get object", done => {
    s3.putObject({ Bucket: bucketName, Key: objectName, Body: message }, (err, data) => {

        s3.getObject({ Bucket: bucketName, Key: objectName}, (err, data) => {
            expect(data.Body.toString('utf-8')).toEqual(message);
            done();
        });
    });
});

Node.jsの方だと、Content-MD5の検証には失敗しないんですよね…なんででしょう…?

とりあえず、動かすところまではなんとかなりました…。