ConoHaオブジェクトストレージで5GBを超えたファイルをアップロードする

[rabbit_swift](https://github.com/AKB428/rabbit_swift)というオブジェクトストレージのクライアント Rubyモジュールを作成しているため、その際判明したオブジェクトストレージの挙動についての記事になります。


#OpenStackのファイルサイズ5GB制限

[ConoHaオブジェクトストレージ](https://www.conoha.jp/guide/guide.php?g=45)はAWSのS3のようにファイルをどかどかアップロードできる便利なストレージサービスなのですが、OpenStackベースのサービスのためアップロードできる1ファイルサイズは**5GB**(正確には5,368,709,120バイト)までという制限があります。

## 5GBを超えるための２つの方法

もちろんこれでは５GB以上のファイルをストレージサービスに格納できなくなるため、使い物になりません。これを回避するためにLargeObjectという概念で5GB以上のファイルを扱えるようになっています。

DLOとSLOという２つの方法がありますが、基本的には５GB以上のファイルを分割し５GB以下にしてからバラバラにアップロードするという単純なアプローチになります。

### Dynamic Large Object (DLO)
DLOはオブジェクトストレージに最初に実装されていたモードで、SLOに比べて信頼性が落ちるため今回は取り扱いません。

### Static Large Object　(SLO)
SLOは分割したファイルの情報を**マニフェストファイル**というJSONに格納し、分割したファイルと共にアップロードします。
ダウンロードする時はマニフェストファイルのパスをダウンロードすれば自動的に結合されたファイルになります。

#### Static Large Object 概念図

SLOの概念図を7GBのファイル(test.zip)を/testコンテナにアップロードするケースを考えて記載します

![StaticLargeObject.png](https://qiita-image-store.s3.amazonaws.com/0/36931/292e28b6-7fe3-27e8-bc01-b2f79838d776.png)

#### SLO処理フロー
* 7GB のファイル test.zipを5GB以下に分割する、この時分割したファイル名は昇順で結合できるようなファイル名にしておく。 (test.zip_001, test.zip_002)
* test.zip_001 をアップロードする
* test.zip_002 をアップロードする
* test.zip_001 の MD5SUMを計算する
* test.zip_002 の MD5SUMを計算する
* (★1) 算出したMD5SUMをetagとしてmanifest.jsonを作成する
* (★2) 作成したmanifest.jsonを/testコンテナに**test.zipとしてアップロードする**

#### (★1) manifest.json

manifest.jsonの例は以下

```json:manifest.json
[
  {
    "path": "https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001",
    "etag": "ab30f25f120fead24c4eaea63f5fe096",
    "size_bytes": 104857600
  },
  {
    "path": "https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002",
    "etag": "81160d082d081cb92fae1047f37f274b",
    "size_bytes": 77011881
  }
]
```

* etagに分割したファイルのMD5sumを格納
* sizeに分割したファイルサイズをそれぞれ格納


#### (★2) manifest.jsonをアップロードする際の必須HTTPヘッダー値

| ヘッダー名  | 値　　　　　 | 備考         |
|:-----------|:------------|:------------:|
| X-Auth-Token | token値| オブジェクトストレージとの通信では常に必要　|
| X-Static-Large-Object |  true  |          |
| X-Object-Manifest     |  test/test.zip_  |  /格納先のコンテナ/オリジナルのファイル名 + _ をつける。ここをつけ間違うとエラーになる、結構重要。        |
|Content-Type | application/json | たぶん必須 |

### (*2) manifest.jsonをアップロードする際のHTTP URLパラメーター

対象のURLに**multipart-manifest=put**のクエリーをつける必要があります。

例
``https://objectstore-r1nd1001.cnode.jp/v1/hoge/test/test.zip?multipart-manifest=put``




・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・
・・と結構大変なフローを組まないと行けません。:scream:
アップロードする時は面倒ですがダウンロードする時はマニフェストファイルのパスをダウンロードすると自動的にOpenStackオブジェクトストレージがファイルを結合して返してくれます。


## slo_client.rb でアップロードする場合

[rabbit_swift](https://github.com/AKB428/rabbit_swift)をgit cloneしbundle installするとbinフォルダのslo_client.rbが使えます。

* -s で送るファイルを指定
* -d で送り先のコンテナを指定
* -c でオブジェクトストレージ接続情報が記載されているjsonを指定
* -l で分割するファイルサイズを指定 バイト指定か(KB/MB/GB)単位

```
bundle exec ruby -I./lib bin/slo_client.rb -s ~/Downloads/test.zip -d /test -c ../chino/conf/conf.json -l 100MB
/Users/Siori/Downloads/test.zip_0001
/Users/Siori/Downloads/test.zip_0002
[{"path":"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001","etag":"ab30f25f120fead24c4eaea63f5fe096","size_bytes":104857600},{"path":"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002","etag":"81160d082d081cb92fae1047f37f274b","size_bytes":77011881}]
upload_url -> https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0001
/Users/Siori/Downloads/test.zip_0001
http_status -> 201
upload OK
upload_url -> https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip_0002
/Users/Siori/Downloads/test.zip_0002
http_status -> 201
upload OK
dest_path->https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test
"https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test"
"test.zip"
"/test/test.zip"
"test/test.zip"
#<URI::HTTPS:0x007feadb823c50 URL:https://objectstore-r1nd1001.cnode.jp/v1/93a6500c0a1e4c68b976e5e46527145c/test/test.zip?multipart-manifest=put>
201
```

### 実際にConoHaオブジェクトストレージにSLOをアップロードしたファイルの図

#### ConoHaコントロールパネル

![CBIMGsfdsfsd001.png](https://qiita-image-store.s3.amazonaws.com/0/36931/361cd921-3f69-e6b6-b4b6-e60b8afd6602.png)

![slo001.png](https://qiita-image-store.s3.amazonaws.com/0/36931/0f089460-4794-5a2d-88a5-f316fe7463c0.png)

実際に５GB以上のファイルがアップロードされているのがわかる。

#### CyberDuck

![CBIMGsfdsfsd003.png](https://qiita-image-store.s3.amazonaws.com/0/36931/300d7bd1-859d-b667-133c-6a6740aa09b0.png)
![CBIMGsfdsfsd002.png](https://qiita-image-store.s3.amazonaws.com/0/36931/c918b42f-8355-f2bb-171b-11a3cdff4ac0.png)
![CBIMGadasdad001.png](https://qiita-image-store.s3.amazonaws.com/0/36931/e6b0b2de-a2d2-99ce-490a-0a1edd8b8888.png)

※CyberDuckはSLOに対応していないのか、マニフェストファイルのサイズがJSON自体のサイズになっている点に注意。

## Static Large Objectで分割したファイルをダウンロードする

SLOで分割したファイルをダウンロードする際は別段特別なアプローチは必要じゃありません。
ただ単にマニフェストファイルのパスをダウンロードすればオブジェクトストレージがファイルを結合して返してくれます。

[rabbit_swift](https://github.com/AKB428/rabbit_swift)のbin/get_object.rbの場合は以下

* -t で対象のファイルパスを指定
* -d で保存ディレクトリを指定
* -c でオブジェクトストレージ接続情報のJSONを指定

```
$ bundle exec ruby -I./lib bin/get_object.rb -t /test/test.zip -d ./private -c ../chino/conf/conf.json
X-Object-Meta-Original-File-Md5sum = efe1sfeda2fdcbkije17ea1e8ea7366
Content-Length = 181869341
Etag = "4c2b17bf693a16448c44028980fb30b0"
Accept-Ranges = bytes
Last-Modified = Sun, 22 Feb 2015 16:40:38 GMT
X-Object-Manifest = test/test.zip_
X-Timestamp = 1424623237.62124
X-Static-Large-Object = true
Content-Type = application/json
X-Trans-Id = txb07ccbdc4a1840498e137-0054ea1b2c
Date = Sun, 22 Feb 2015 18:08:44 GMT
./private/test.zip
181869341 --> original_file_size
181869341 --> save_file_size
OK! file size
efe1sfeda2fdcbkije17ea1e8ea7366 --> original_file_md5
efe1sfeda2fdcbkije17ea1e8ea7366 --> save_file_md5
OK! MD5 checksum
```


## ConoHaでSLOを使う時のいくつかの問題

### ConoHaコントロールパネルからファイル落とせない！

SLOに限った問題ではないですがある一定のファイルサイズになるとConoHaのコントロールパネルからファイルをダウンロードできないので、CyberDuckやswiftコマンドや自作のソフトを使わざるを得なくなります。

### アヒルが対応してないっぽい！

ConoHaでオブジェクトストレージを使うときのGUIソフトの定番はCyberduckなのですが、SLOでアップロードしたファイルをダウンロードするとタイムアウトエラーが発生しダウンロードが完遂できません。
コンテナのファイルリストの表示がそもそもSLOに対応してないような表示なので現状は期待できない感じです。

### ファイルダウンロード後のMD5チェックができない！

ファイル分割していない通常のオブジェクトの場合、HTTPヘッダーのetagにMD5値が格納されているため、オブジェクトストレージからファイルをダウンロードした際はその値とダウンロードしたファイルのMD5チェックサムを比較すればいいのですが、SLOの場合は結合したファイルのMD5SUM値をどうも保持していないようです。マニフェストファイルのEtagはOpenStackの公式サイトやConoHaのRESTマニュアルには**「MD5ハッシュ値とETagを連結した値が表示されます。」**と書かれていますが返ってくる値はダブルクウォートがついたMD5と同じ長さの謎の値のためこの値が何を意味しているのかは不明です。

公式のpython-swiftクライアントもこの値でのチェックは諦めており、SLOの場合はMD5チェックをSKIPするという挙動に見えます。(その変わりファイルサイズが一致するかだけ確認している）
https://github.com/openstack/python-swiftclient/blob/06c73c6020e5af873e3ce245a27035da3448de7b/swiftclient/service.py#L330

#### Static Large Objectのダウンロード後のMD5チェックサム一致を確認する方法

・・とは言え分割したファイルを結合した際にファイルサイズだけで一致性を確認するのもナンセンスなため、私が作成した[rabbit_swift](https://github.com/AKB428/rabbit_swift)モジュールではマニフェストファイルのメタデータにオリジナルファイルののMD5SUMを埋め込んで、ダウンロード後にそれを確認するというアプローチにしています。

(ソフトは[chinoコマンド](https://github.com/AKB428/chino))
```
$ bundle exec ruby chino.rb -g /test/test.zip
https://objectstore-r1nd1001.cnode.jp/v1/hogehoge/test/test.zip
X-Object-Meta-Original-File-Md5sum: ff71367da2fdcv4c59e1ffa1e8ea7399
// ↑　このメタデータをアップロードする時のHTTPヘッダーに埋め込んでおく
Etag: "4c2b17bf693a16448c44028980fb30b0" 
// ↑  これが何の値かはマジで不明 サーバーのコードを読みとくしか無いだろう。
X-Object-Manifest: test/test.zip_
X-Static-Large-Object: true
Content-Type: application/json
```