IBM Cloud Object Storage(ICOS)のAPI Callの課金について

# はじめに

Cloud上で非常に安価で大量のデータを格納できるストレージとしてICOSがよく利用されます。ICOSの課金の仕組みは２つの要素で決まります。
1つ目は月次保管データ・ボリュームの平均、２つ目はシステムによって処理される操作要求の総数です。
今回は２つ目の操作要求を対象として、ICOSのデータのアップロード、ダウンロード、リストなどを実行する際に発生するAPI Callがどのようにカウントされるのか確認することを目標に検証を行いました。


検証結果として **APIコマンド1回に対して、class Aもしくはclass Bのカウント1回。API 1回で複合的にclass A, class Bがカウントされることはありませんでした。** 詳細な検証結果は本記事の後半で記載してあります。


# API Callの課金について
API Callは要求内容によってClass AとClass Bの2種類が存在します。

Class Aの要求には、変更またはリスト作成が関係します。 
Class Aの要求に該当する例としてはバケットの作成、オブジェクトのアップロードまたはコピー、バケットのリスト、バケットの内容のリスト、ACLの設定、およびCORS 構成の設定などが挙げられます。

Class Bの要求は、システムからのオブジェクトなどの取得に関連する要求です。
Class Bの要求に該当する例としてはオブジェクトおよびメタデータの取得が挙げられます。

また、バケットまたはオブジェクトをシステムから削除する場合はClass A・Bどちらにも該当せず、料金は発生しません。

クラス要求の金額はストレージクラスによって異なりますが、Smart Tierの場合はClass Aは1000回あたり¥0.774、Class Bは10,000回当たり¥0.774となっています。

また、Class AとClass Bの量に応じてSmart TierのデータプランがHot・Cool・Coldの３段階に分かれます。```storage```(具体的には保管されたデータの合計量(GB))と```retrievals```(取得されたデータの合計ボリューム(GB))、```requests```(クラス A (書き込み) 要求の数に、クラス B (読み取り) 要求の数の 1/10 を加算した合計)の関係値によって以下の通りに分類されます。

Hot : ```requests > 1000 x (storage - retrievals)```
Cold : ```requests < (storage - retrievals)```
Cool : その他

より詳細な料金はポータルの製品情報からご確認ください。
https://cloud.ibm.com/objectstorage/create#pricing



# 手順

本記事で実施した検証内容は以下の通りです。
1. curlを使用した際のAPI Callの確認
1. Pythonを使用した際のAPI Callの確認
1. バケット内に大量のファイルが存在する場合のリスト取得で発生するAPI Callの確認

また、本記事の構成図は以下のようになっています。

![スクリーンショット 2024-03-05 1.06.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/3cd9d989-16ed-62ca-b25a-acfa1eb8aeab.png)


## 使用する環境
本記事で使用する環境は以下のとおりです

- IBM Cloud Object Storage(ICOS)の東京リージョンのBucket

    ![スクリーンショット 2024-02-12 18.03.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/ca571200-c2b8-961b-ad54-5360e01fa2e6.png)
    バケットのログをActivity Trackerに送信するようにあらかじめ設定しておきます。

    ![スクリーンショット 2024-02-12 18.03.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/94a0e9a9-0967-f3eb-b1c0-412a306681a0.png)

- 東京リージョンのIBM Cloud Activity Tracker

    ![スクリーンショット_2024-02-12_18_10_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/8bce2f20-bb1d-f1cd-fe3d-3d7426eb687e.png)

また、pythonの環境としてGoogleのcolaboratoryを利用しています。

## 1. curlを使用した際のAPI Callの確認
まずはじめにcurlを使用してICOSの操作を実施し、API Callの発生を確認します。
作業内容は[こちらのdocs](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-curl)を参考に進めています。
また、外部からICOSへのアクセスのためのトークン取得はこちらのQiita記事を参考にしています。

https://qiita.com/y_tama/items/6e31922eb9504502d568

手順は以下の通りです。

1. ICOSアクセスのためのtokenの取得
1. curlを使用したICOSのバケットへファイルのアップロード・リスト・ダウンロード
1. Billing APIからAPI Callの確認

### 1-1. ICOSアクセスのためのtokenの取得
まずはじめにICOSへアクセスするためのbearer tokenを取得します。tokenの取得コマンドは以下の通りです。```[apikey]```には事前に取得したapikeyを代入します。apikeyの取得方法についてはこちらの記事を参考にしてください。

https://qiita.com/nishikyon/items/fa8b951267efbe911fe7

```
$  curl -k -X POST --header "Content-Type: application/x-www-form-urlencoded" --header "Accept: application/json" --data-urlencode "grant_type=urn:ibm:params:oauth:grant-type:apikey" --data-urlencode "apikey=[apikey]" "https://iam.cloud.ibm.com/identity/token"
{"access_token":"bearertoken","refresh_token":"not_supported","ims_user_id":9985776,"token_type":"Bearer","expires_in":3600,"expiration":1709474315,"scope":"ibm openid"}%
```
コマンドを打つと、access_tokenの後ろにbearer tokenが出力されます(上の```"bearetoken"```に当たる部分)。
注意点として生成したtokenは有効期限が1時間となっているため、期限切れの場合再び作成し直す必要があります。

### 1-2. curlを使用したICOSのバケットへファイルのアップロード・リスト・ダウンロード

次に先ほど取得したbearer tokenを使用してcurlからICOSを操作を実施します。
以下、バケットの操作コマンドとなります。

```:ファイルのアップロード
$  curl -X PUT “https://s3.jp-tok.cloud-object-storage.appdomain.cloud/[バケット名]/[登録ファイル名]” -H “Authorization: bearer [bearer token]” -T “[送付したいファイル]”
```

```:ファイルのダウンロード
$  curl "https://s3.jp-tok.cloud-object-storage.appdomain.cloud/[バケット名]/[ダウンロードファイル]" -H "Authorization: bearer [bearer token]" --output [ダウンロードファイル保存名]
```

```:バケット内ファイルのリスト
curl "https://s3.jp-tok.cloud-object-storage.appdomain.cloud/[バケット名]" -H "Authorization: bearer [bearer token]"
```
今回は東京リージョンのバケットのためエンドポイントが```s3.jp-tok.cloud-object-storage.appdomain.cloud```となっており、別リージョンの場合は対応したエンドポイントに変更してください。また、[bearer token]・[バケット名]などの中身は対応したものを代入してください。

### 1-3. Activity Tracker(or API Billing)からAPI Callの確認

ICOSの操作後にAPI Callがどのくらい発生しているか２通りの手段で確認します。

#### 1-3-1. Activity Trackerを使用した確認方法

Activity Trackerに流れるログを確認することでAPI Callがどれくらい発生しているか確認することができます。
今回は以下の操作を実施してログを確認します。

1. sample1.txtファイルをICOSのバケット上にアップロード
1. sample1.txtファイルをICOSのバケットからダウンロード
1. ICOSのバケット内のファイルのリストアップ

これらの操作を先ほど記載したcurlを利用して実施した後、Activity Trackerで確認したものが以下の画像となります。

![スクリーンショット_2024-03-04_21_31_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/fd6058ab-3615-1101-7538-7e3d3bd90e33.png)

それぞれ詳細を確認すると以下の様になっています。

ファイルのアップロード
![スクリーンショット 2024-03-04 23.07.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/4238fd5c-1e0f-75f6-0081-5a5296f5f68a.png)

ファイルのダウンロード
![スクリーンショット 2024-03-04 23.07.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/bed2f7e6-f1fd-cd6d-0a2d-2cb2d86ef319.png)

バケット内ファイルのリスト
![スクリーンショット 2024-03-04 23.07.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/85442d56-38a7-0df2-cfee-a8a185fd1d5e.png)

これらを確認するとファイルのアップロードではputコマンド(Class A)、ファイルのダウンロードではgetコマンド(Class B)、バケット内ファイルのリストではバケットリストのためのgetコマンド(Class B)が呼び出されていることが確認できます。

#### 1-3-2. Billing APIからの確認方法

Billing APIを見ることでAPI Callがどのくらい発生したのかを確認できます。API Billingを入手するためには```resouce_incetance_id```、```account_id```が必要なのでこれらを確認します。

##### resouce_incetance_idの確認
ポータルのICOS画面から**サービス資格情報**→**新規資格情報**を選択します。

![スクリーンショット_2024-02-09_16_48_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/c405e12a-6c52-d196-de86-0718f3a89fab.png)

任意の名前をつけ、**ロール**を管理者、**HMAC資格情報を含める**をオンにして作成します。

![スクリーンショット_2024-02-09_16_50_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/c2d7b09e-6e7c-de64-c720-b2bcd68d5926.png)

作成後、**サービス資格情報**の画面から先ほど作成した資格情報が確認できるのでその中のresource_instance_idをコピーしておきます。

![スクリーンショット_2024-02-09_16_57_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/a070982a-408a-0e00-0d7b-bccff809ecc9.png)

##### account_idの確認

次に```account_id```を確認します。
ポータルの右上にある**管理**→**アカウント**を選択します。

![スクリーンショット_2024-03-04_16_31_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/3cbd80d7-bf36-1c28-405f-ccc7069b3514.png)

続いて左側のメニューから**アカウント設定**を選択するとアカウントIDが表示されるのでコピーしておきます。

![スクリーンショット_2024-03-04_16_22_39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/a5461684-4974-6c01-e6fe-81248098c71b.png)


##### billing APIの取得

取得した情報をもとにbilling APIを入手します。
```:billingの取得
curl -X "GET" "https://billing.cloud.ibm.com/v4/accounts/[ACCOUNT_ID]/resource_instances//usage/[BILLING_MONTH]?resource_instance_id=[resource_instance_id]&region=[region]" \
-H "Authorization: Bearer [bearer token]" --output billingbefore_2402.json
```
[]で括られてるものは入手した情報を代入します。[region]は対象となるリージョン(今回ならjp-tok)、[BILLING_MONTH]は取得したいbilling APIの月(2024年2月なら2024-02)を指定します。
コマンド実行後、billing APIがjsonファイルとして出力されます。


## 2. pythonを使用した際のAPI Callの確認

続いてpythonを使用した際にAPI Callの量が変化するのかを確認します。

手順は以下の通りです。

1. ICOSのバケットへファイルのアップロード・リスト・ダウンロード
1. Activity Tracker(or API Billing)からAPI Callの確認

### 2-1. ICOSのバケットへファイルのアップロード・リスト・ダウンロード

pythonを使用してICOSバケットの操作を実施します。
まず、pythonで使用するライブラリーをインポートします。

```python
import ibm_boto3
from ibm_botocore.client import Config, ClientError
```
次にpythonからICOSへアクセスするためにはICOS資格情報が必要なので、curlからアクセスする際に作成したICOS資格情報をコピーします。

```python
credencials = {
#資格情報を貼り付ける
}
```
続いて入手した資格情報をもとに接続を作成します。Endpointは今回東京リージョンのICOSのため、東京リージョンのパブリックエンドポイントを使用しています。他のリージョンを使用する際はポータルのICOSのページ左側の**エンドポイント**から使用するリージョンのエンドポイントを確認してください。
```python
COS_ENDPOINT = "https://s3.jp-tok.cloud-object-storage.appdomain.cloud"

COS_API_KEY_ID = credencials["apikey"] 

COS_INSTANCE_CRN =  credencials["resource_instance_id"]  

# Create resource
cos = ibm_boto3.resource("s3",
    ibm_api_key_id=COS_API_KEY_ID,
    ibm_service_instance_id=COS_INSTANCE_CRN,
    config=Config(signature_version="oauth"),
    endpoint_url=COS_ENDPOINT
)
```

#### ICOSのバケットを操作する関数を作成する
ICOSとの接続が可能になったのでICOSのバケットを操作する関数を作成します。基本的なものに関してはICOSの[docs : pythonの使用](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-python)を参考に作成しています。

```python:テキストファイルを作成しアップロード
def create_text_file(bucket_name, item_name, file_text):
    print("Creating new item: {0}".format(item_name))
    try:
        cos.Object(bucket_name, item_name).put(
            Body=file_text
        )
        print("Item: {0} created!".format(item_name))
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
    except Exception as e:
        print("Unable to create text file: {0}".format(e))
```
```python:バケット内の指定したファイル取得
def get_item(bucket_name, item_name):
    print("Retrieving item from bucket: {0}, key: {1}".format(bucket_name, item_name))
    try:
        file = cos.Object(bucket_name, item_name).get()
        print("File Contents: {0}".format(file["Body"].read()))
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
    except Exception as e:
        print("Unable to retrieve file contents: {0}".format(e))
```
```python:バケット内のリスト
def get_bucket_contents(bucket_name):
    print("Retrieving bucket contents from: {0}".format(bucket_name))
    try:
        files = cos.Bucket(bucket_name).objects.all()
        for file in files:
            print("Item: {0} ({1} bytes).".format(file.key, file.size))
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
    except Exception as e:
        print("Unable to retrieve bucket contents: {0}".format(e))
```
```python:バケット内のリスト(prefix指定)
def get_bucket_contents_prefix(bucket_name, search_prefix):
    print("Retrieving bucket contents from: {0}".format(bucket_name))
    try:
        files = cos.Bucket(bucket_name).objects.filter(Prefix=search_prefix)
        for file in files:
            print("Item: {0} ({1} bytes).".format(file.key, file.size))
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
    except Exception as e:
        print("Unable to retrieve bucket contents: {0}".format(e))
```
それぞれの関数を実行するとICOSの操作が可能です。

### 2-2. Activity TrackerからAPI Callの確認
先ほど作成した関数を使用してICOSを操作し、Activity TrackerからでAPI Callが送られているかを確認します。
まずは```create_text_file```関数を使用してファイルのアップロードをおこないます。
```python
create_text_file("test-request","test.txt", "hoge")
```

ポータルで確認すると先ほど作成したファイルが追加されています。

![スクリーンショット 2024-02-12 18.30.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/5712e202-4906-305c-02be-7bf3db0f06e7.png)

また、Activity Trackerで確認すると```bucket.get```のAPIが呼び出されています。

![スクリーンショット 2024-02-12 18.34.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/82d87a58-c28b-f9be-309d-cb3804961307.png)

同様にリストとファイル取得も試してみます。

**リスト取得**
```python
get_bucket_contents("test-request")
```
出力：
Retrieving bucket contents from: test-request
Item: test.txt (4 bytes).

Activity Tracker：
object.get
![スクリーンショット 2024-02-12 19.04.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/caf51bcd-a4c9-7cc8-8a14-4ba41a0760a3.png)



**ファイル取得**
```python
get_item("test-request", "test.txt")
```
出力：
Retrieving item from bucket: test-request, key: test.txt
File Contents: b'hoge'

Activity Tracker：
object.get
![スクリーンショット 2024-02-12 19.03.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/2acbaf8d-3c7e-9346-4829-22342ea7afc3.png)

## 3. バケット内に大量のファイルが存在する場合のリスト取得で発生するAPI Callの確認

基本的にバケットからファイルのリストを取得する際に発生するクラス要求はClass AのGet要求1回ですが、[docs](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-compatibility-api-bucket-operations&locale=ja#compatibility-api-list-objects-v2)によるとバケット内に1000以上のファイルが存在する場合は1000ファイル毎に要求が発生すると記載がありましたので、検証してみます。

まずはバケット内に大量のファイルをアップロードします。
```python
def mult_object_upload(bucket_name, num, prefix):
  for i in range(num):
    item_name = "{0}/testfile{1}".format(prefix, i)
    file_text = "{0}/testfile{1}".format(prefix, i)
    print("Creating new item: {0}".format(item_name))
    try:
        cos.Object(bucket_name, item_name).put(
            Body=file_text
        )
        print("Item: {0} created!".format(item_name))
    except ClientError as be:
        print("CLIENT ERROR: {0}\n".format(be))
    except Exception as e:
        print("Unable to create text file: {0}".format(e))
```
試しにprefixを変更したファイルを1000個ずつ合計4000個アップロードします。
```python
mult_object_upload("test-request", 1000, "test-1")
mult_object_upload("test-request", 1000, "test-2")
mult_object_upload("test-request", 1000, "test-3")
```
上記コマンド実施後、バケット内にはtest-1/testfilexxx・test-2/testfilexxx・test-3/testfilexxxがそれぞれ1000ファイルの3000ファイル+元々バケットにあったファイル3個の合計3003ファイルが存在しています。

### 3-1. pythonでlist取得する場合
```python
get_bucket_contents("test-request")
```

Activity Tracker：
![スクリーンショット_2024-02-12_20_26_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/6506a5af-d22f-35e8-5992-02476f2eab9f.png)

Activity Trackerを見ると、1回のリスト取得で4回分のクラス要求が発生していることが確認できます。
今度はprefixを指定してバケット内の1000個だけをリスト化してみます。
以前作成した関数を使用してファイル名の頭が"test-1"(test-1/testfile0~test1/testfile999までの1000ファイル)のみを指定してリストを取得します。
```python
get_bucket_contents_prefix("test-request", "test-1")
```

今回は1000個だけをリスト化しているのでActivity Trackerを見ると、1回のリスト取得で1回だけクラス要求が発生していることが確認できます。

![スクリーンショット_2024-02-12_20_30_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625926/fd0a4a88-562b-6842-ecad-da53b580c1f3.png)

### 3-2. curlでlist取得する場合

curlでも同様にlist取得してみるとActivity Trackerでは1行分のログしか追加されません。
そこでlistをtextファイルとして出力して確認してみます。

```:バケット内ファイルのリストをtext出力
curl "https://s3.jp-tok.cloud-object-storage.appdomain.cloud/test-request" -H "Authorization: bearer [bearer token]" --output list.txt
```

出力したtextファイルを確認してみると1000ファイルのみしか出力されていませんでした。
このことから、curlでは1000ファイルを超えたリスト要求があった場合1000ファイルまでしか出力されないことがわかります。

本記事の検証内容は以上となります。

# 結論
本記事で検証した結果は以下の通りとなりました。
1. curlからICOSを操作した場合、ダウンロードではClass Bが、アップロード・リスト取得(ファイル1000以下)ではClass Aがそれぞれ1回の操作に対して1回発生した
1. pythonからICOSを操作した場合、ダウンロードではClass BのAPI Call、アップロード・リスト取得(ファイル1000以下)ではClass AのAPI Callがそれぞれ1回の操作に対して1回発生した
1. 1000を超えるファイルのリストを実行した場合、pythonは分割されて複数回のAPI Callが発生したのに対し、curlでは1000個のファイルのみリストされたため、1回だけAPI Callが発生した

以上の結果から基本的にはdocsに記載があった通りにICOS操作に対して対応したClass AもしくはClass BのAPI Callが発生していることが確認できました。

ちなみにポータルで操作すると、ポータル上でバケットやファイルを確認するだけでAPI Callが発生します。

また、ポータルやバックアップソフトなどAPI Callが隠蔽されているアプリケーションやソフトウェアなどを利用する場合はICOS操作で想定される以上のAPI Callが発生する可能性があるのでご注意ください。


例として、下の記事ではs3cmdを利用していますが、大容量のファイルを転送する際にファイルが分割されるため、一回のアップロードに複数回のAPI Callが発生しています。

https://qiita.com/y_tama/items/bb9c553ba8f35f12f8a4#アップロードの動作確認