[Cloudian #4] Python(boto3)で、オブジェクトストレージのファイル転送してみる


## はじめに
[Cloudian](https://cloudian.com/jp/)は、AWSのS3完全互換のAPIを持ったオブジェクトストレージです。
[前回](https://qiita.com/yamahiro/items/14a7070d733dbad09597)は、Python(boto3)で、バケットを新規作成してみました。

今回は、Python(boto3)で、オブジェクトストレージのファイル転送をしてみたいと思います。

## S3 Transfers によるファイル転送処理 (ファイルのアップロード/ダウンロード)
boto3 には、より簡易に/より効率的にアップロード/ダウンロード操作を行うことができる S3
Transfers というモジュールが用意されています。

S3 Transfers は、以下のような機能を提供しています。

- 指定したファイルサイズを上回った際に、自動的にマルチパート転送に切り替わります。 
- 並列処理でファイルのアップロード/ダウンロードを実行します。
- ネットワークの最大帯域幅に応じて、ファイルを転送します。
- 転送状況をモニタリングするために、転送の進捗状況をコールバックできます。
- ファイルのアップロード時に、リトライを実行します。

S3 Transfers のパラメータのデフォルト値には最適な値が設定されていますが、以下のパラメータ設定を変更することもできます。
- マルチパート閾値サイズ
- 並行ダウンロード処理の最大数
- 使用するネットワーク最大帯域幅
- ソケット・タイムアウト値
- リトライ回数

S3 Transfers のパラメータを変更するには、変更対象のパラメータの設定値を引数に指定して、
TransferConfig オブジェクトを作成します(事前に "boto3.s3.transfer.TransferConfig" をインポートする必要があります)

```python
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

config = TransferConfig(
    multipart_threshold = 8 * 1024 * 1024,
    max_concurrency = 10,
    multipart_chunksize = 8388608,
    num_download_attempts = 10,
    max_io_queue = 100
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client, config)
```


## 1. S3Transfer オブジェクトの作成
S3 Transfers の機能を利用するためには、まず S3Transfer オブジェクトを作成します。   
事前に、boto3.s3.transfer.S3Transfer をインポートしておく必要があります。本書の例では、既に S3Transfer はインポート済みの状態になっています。

S3Transfer()の引数に、既に作成済みの S3Client オブジェクトを渡して S3Transfer オブジェクトを作成します。S3Client オブジェクトのみを引数として渡し S3 Transfers オブジェクトを作成した場合には、S3 Transfers のパラメータは全てデフォルト値が使用されます。

```python
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client)
```


## 2. ファイルのアップロード / S3Transfer.upload_file()
S3 Transfers を使用して Cloudian にファイルをアップロードするには、 upload_file()を使用します。  
extra_args 引数を設定して upload_file()を呼び出すこと により、アップロード時にオブジェクト(ファイル)の ACL を設定したり、メタデータを付加したり、 暗号化を行ったりできます。

### シンプルなファイルのアップロード  
以下の例では、ローカルにあるファイル「10mb.dat」を、 Cloudian のバケット「pythonbucket1」にキー「10mb.dat」を設定してアップロードして います。

```python:test1.py
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client)

# ファイルのアップロード実行
transfer.upload_file('fileup/10mb.dat', 'pythonbucket1', '10mb.dat')
```

下図は、 upload_file()の使用例で Cloudian にアップロードしたファイルを Cloudant Mamagement Console（以下CMC） の オブジェクト画面から参照したものです。

![3-3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962155/77f047ca-d875-b092-0ff7-f0ad4a461876.png "3-3-2.png")


### アップロード時に ACL、メタデータ、暗号化を指定
以下の例では、ローカルにあるファイル「10mb.dat」を、Cloudian のバケット「pythonbucket1」にキー「10mb.dat」を指定し、extra_args を設定してオブジェク トの ACL には「public-read」、3 つのメタデータを付加して「AES256」でサーバーサイド暗号化するように指定してアップロードしています。

```python:test2.py
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client)


# ファイルのアップロード実行
transfer.upload_file(
    'fileup/10mb.dat', 'pythonbucket1', '10mb.dat',
    extra_args={
                'ACL': 'public-read',
                'Metadata': {
                    'Purpose': 'boto3 demo',
                    'Engineer': 'yamahiro',
                    'Company': 'Networld'
               },
               'ServerSideEncryption': 'AES256'
    }
)
```

upload_file()の extra_args パラメータ「ServerSideEncryption」を使用し、AES256 で サーバーサイド暗号化を行うように設定したので、ファイル名の先頭に「🔒」マークが表示され、このファイルが暗号化されていることが分かります。

![3-3-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962155/d32ef35c-4282-5186-3c6f-ce3a308e0823.png "3-3-3.png")

また、下図のようにこのファイルのプロパティを開いてみると、extra_args パラメータの ACL で設定したように、このファイルのアクセス権に「パブリック:読み出し可能('ACL': 'public-read')」にチェックが入っていることが分かります。 

![3-3-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962155/18ec5f2c-9937-37df-a33b-a414d8a962e8.png "3-3-4.png")


## 3. ファイルのダウンロード / S3Transfer.download_file()
S3 Transfers を使用して Cloudian からファイルをダウンロードするには、 download_file()を使用します。extra_args 引数を設定して download_file()を呼び 出すことにより、ダウンロード時にオブジェクトのバージョン ID を指定したりすることができます。

以下の例では、バケット「pythonbucket1」に保存されているキー「10mb.dat」の最新 バージョンのオブジェクトをダウンロードしています。

バケット「pythonbucket1」のバージョニング機能が有効にされていた場合、この例ではバー ジョン ID を指定していないので“最新バージョンのオブジェクト”がダウンロードされます(バージョニ ング機能については、後述します)。

```python:test3.py
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client)

# ファイルのダウンロード実行
transfer.download_file('pythonbucket1', '10mb.dat', 'filelocal/10mb-local.dat')
```

### バージョニング機能が有効な場合
以下の例では、バージョニング機能が有効化されているバケット「pythonbucket1ver」に保存されているキー「10mb.dat」の、バージョン ID が「fe14c26e-1662-4f8f-a754-06bdfcde1d5e」のオブジェクトをダウンロードしています。

```python:test4.py
import boto3

from boto3.s3.transfer import S3Transfer
from boto3.s3.transfer import TransferConfig

client = boto3.client(
    's3',
    endpoint_url='https://xxx.yyy.com'
)

# ファイル転送オブジェクト作成
transfer = S3Transfer(client)

# ファイルのダウンロード実行
transfer.download_file(
    'pythonbucket1ver', '10mb.dat', 'filelocal/10mb-local.dat',
    extra_args={'VersionId': 'fe14c26e-1662-4f8f-a754-06bdfcde1d5e'}
)
```

S3 Transfers を使用したファイルのアップロード/ダウンロード時には、マルチパート閾値サイ ズ(multipart_threshold)が設定されており、そのデフォルト値は「8,388,608 bytes(約 8MB)」になっています。

アップロード/ダウンロード対象のファイルサイズが 8MB よりも大きい場合、S3 Transfers は 自動的に複数のパーツにファイルを分割して同時並行で処理を実行します。

下図は大きなサイズのファイルを S3 Transfers でアップロードを行っているときの、CMC オブジェクト画面のスクリーンショットです。8MB よりも大きなファイルは、自動的にマルチパートアップロードが実行されていることが分かります。

![3-3-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/962155/b80ccdff-6044-26e0-55de-84573c7b628c.png "3-3-5.png")



> ### ※補足: get_object() を使用したファイルのダウンロード
>
> S3Transfers オブジェクトの download_file()を使用したファイルのダウンロード以外に、低レベル API に対応する get_object() を使用してファイルのダウンロードを行うこともできます。

> ```python
> # ファイルのダウンロード実行
> with open('filelocal/10mb-local.dat', 'wb') as f:
>     f.write(client.get_object(
>             Bucket='pythonbucket1ver',
>             Key='10mb.dat',
>             VersionId='fe14c26e-1662-4f8f-a754-06bdfcde1d5e'
>         )['Body'].read()
>     )

> ```
>
> ただしこの場合、上記サンプルコードのように、S3Transfers オブジェクトを使用した方法よりも若干、 コードが複雑になります。同じ操作を実行するために複数の方法があることがありますので、適材適所で どの方法を使用するか決める必要があります。


## 4. まとめ
Python(boto3)で、ファイル転送（アップロード/ダウンロード）をしてみました。

[次回](https://qiita.com/yamahiro/items/ee1112a95185becc189f)も、Pythonでオブジェクトストレージ/Cloudianをいろいろ操作していきたいと思います。