IBM Cloud Block Storage 大掃除－許可ホストのないものをリスト・削除する

:::note warn
2023/12/15
この記事ではキャンセルをリクエストしたが保留中の Storage の確認方法として、activeTransactionCount を利用しています。
月額課金の場合、月末(正確にはアニバーサリービリングデート:ABD)の24時間前にならないとトランザクションが開始しないため、この方法では判断できないことが分かりました。
詳しくは「[IBM Cloud File Storage 大掃除－許可ホストのないものをリスト・削除する/課金方法を確認する](https://qiita.com/6onoda/items/4d7de2883f1e95ef44f7)」をご覧ください。
:::


もう12月です。2022年も終わりですね。
師走といえは大掃除の季節です。今回は、許可ホストのない IBM Cloud Block Storage を利用していないとみなし、大掃除をしたいと思います。

操作には SoftLayer API Python Client を利用します。
* https://softlayer-python.readthedocs.io/en/latest/

使用した環境は、Windows 10 上の wsl の Ubuntu 20.04 です。

```
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:        20.04
Codename:       focal
```

# Username および API Key の作成・確認
事前にClassic InfrastructureのUsernameおよびAPI Keyを確認しておきます。
これは、Classic 環境用のもので IAM 環境のものは使えません。

Docs での手順の記載はこちらです。
* https://cloud.ibm.com/docs/account?topic=account-classic_keys

> IBM Cloud コンソールで、「管理」>「アクセス (IAM)」>**「API キー」に移動し、「クラシック・インフラストラクチャー API キー」**を選択します。
**「クラシック・インフラストラクチャー・キーの作成 (Create a classic infrastructure key)」**をクリックします。

ここですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184930/ae1c11b4-98ae-b7f7-bab5-bc51abd50993.png)

Username は「アカウントID-メールアドレス」の形式です。
こちらも参考にしてください。

[IBM Cloud: Classic InfrastructureのslcliコマンドをCentOS7でセットアップする - 4. slcliの設定](https://qiita.com/testnin2/items/8c2bf7990ef55b4abf7d#4-slcli%E3%81%AE%E8%A8%AD%E5%AE%9A)


# SoftLayer API Python Client を導入する

導入方法はこちらです。
* https://softlayer-python.readthedocs.io/en/latest/install/

今回は pip で導入します。

```
sudo apt-get update
sudo apt install python3-pip
sudo pip install softlayer
```

「slcli --version」でバージョンが表示されれば導入は完了です。

```
$ slcli --version
Current: slcli (SoftLayer Command-line) v6.1.2
Latest:  slcli (SoftLayer Command-line) v6.1.2
```

# SoftLayer API Python Client の使用

## client instance の作成

まず、client instance を作成します。
* https://softlayer-python.readthedocs.io/en/latest/api/client/#getting-started

Python コードのパラメーターに直接指定することもできます。

```
import SoftLayer
client = SoftLayer.create_client_from_env(username='YOUR_USERNAME', api_key='YOUR_API_KEY')
```

また、環境変数にセットしてコードに含まないことも可能です。今回は、こちらのパターンを利用しましょう。

```
$ export SL_USERNAME=YOUR_USERNAME
$ export SL_API_KEY=YOUR_API_KEY
$ python3
>>> import SoftLayer
>>> client = SoftLayer.create_client_from_env()
```

## Manager の取得

SoftLayer API Python Client では操作対象ごとに Manager が用意されています。
* https://softlayer-python.readthedocs.io/en/latest/api/client/#managers

Block Storage の Manager は「SoftLayer.managers.block.BlockStorageManager(client)」になります。
* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/

これ使用する Block Storage Manager のメソッドの情報も、このページにあります。

下記のように Block Storage Manager を取得します。
```
>>> bsm = SoftLayer.managers.block.BlockStorageManager(client)
```

## Block Storage 一覧の取得

「list_block_volumes」でBlock Storage 一覧の取得が可能です。
* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/#SoftLayer.managers.block.BlockStorageManager.list_block_volumes

下記のように一覧を取得します。

```
>>> bs_list=bsm.list_block_volumes(datacenter=None, username=None, storage_type=None, order=None)
```

一つ目の要素から、内容を pprint で確認します。
要素は辞書型で「id」に volume id が入っています。 

```
>>> import pprint
>>> pprint.pprint(bs_list[0])
{'activeTransactionCount': 0,
 'capacityGb': 20,
 'id': 123456789,
 'lunId': '7',
 'replicationPartnerCount': 0,
 'serviceResource': {'backendIpAddress': 'fsf-tok0401e-fz.service.softlayer.com',
                     'datacenter': {'name': 'tok04'},
                     'id': 41504,
                     'name': 'Storage Type 02 Aggregate stxf-tok0401e',
                     'type': {'type': 'NETAPP_STOR_AGGR'}},
 'serviceResourceBackendIpAddress': '10.201.XXX.XXX',
 'username': 'IBM02SEL1234567-18'}
```

## 許可ホストの取得

許可ホストの取得には「get_volume_access_list」を利用します。

* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/#SoftLayer.managers.block.BlockStorageManager.get_volume_access_list

リスト内のひとつの要素の許可ホストを確認しましょう。

許可ホストは、'allowedHardware'、'allowedIpAddresses'、'allowedSubnets'、['allowedVirtualGuests' の4つの部分に分かれています。
許可ホストが無い場合、各々に空の辞書が返ります。下記のように 4 つとも空であれば、どのホストからもアクセスされる可能性の無い、使われていないホストです。

```
>>> pprint.pprint(bsm.get_volume_access_list(bs_list[60]['id']))
{'allowedHardware': [],
 'allowedIpAddresses': [],
 'allowedSubnets': [],
 'allowedVirtualGuests': [],
 'id': 123456789}
```

## ボリュームのキャンセル

ボリュームのキャンセルは「cancel_volume」で行います。

* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/#SoftLayer.managers.block.BlockStorageManager.cancel_volume

```
>>> bsm.cancel_volume(bs_list[XXX]['id'], reason='No longer needed', immediate=False)
```

「cancel_block_volume」もありますが、内部で「cancel_volume」を呼び出しているだけです。

* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/#SoftLayer.managers.block.BlockStorageManager.cancel_block_volume


```
    def cancel_block_volume(self, volume_id, reason='No longer needed', immediate=False):
        """Cancels the given block storage volume.

        :param integer volume_id: The volume ID
        :param string reason: The reason for cancellation
        :param boolean immediate_flag: Cancel immediately or on anniversary date
        """
        return self.cancel_volume(volume_id, reason, immediate)
```

即時キャンセルをしない場合、24時間は「キャンセル待ち」の状態になりキャンセルを取り消すことができます。
しかし、この状態では、再度のキャンセルはできません。

* https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-block-storage-faqs&locale=ja#cancelstorage

> Cloud コンソールのキャンセル・アクションが使用できないため、Block Storage ボリュームをキャンセルできません。 どうなっているのでしょうか?
>
>このストレージ・デバイスに関する取り消しプロセスが進行中なので、取り消しアクションを使用できなくなっています。 ボリュームは再利用処理が施されるまで少なくとも 24 時間引き続き表示されます。 デバイス名の隣に砂時計か時計のアイコンが表示されて、待機期間内であることが示されます。 少なくとも 24 時間待機期間があるので、必要に応じて取り消し要求を無効にすることができます。

実際にキャンセルを要求すると、exception が発生します。

```
>>> bsm.cancel_volume(123456789, reason='No longer needed', immediate=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/SoftLayer/managers/storage.py", line 547, in cancel_volume
    raise exceptions.SoftLayerError(
SoftLayer.exceptions.SoftLayerError: Storage Volume was already cancelled
```

「get_block_volume_details」で確認すると、キャンセル済み・削除待ちのものは、activeTransactionCount が 1 にセットされ、activeTransactions に削除待ちであると記載があります。

* https://softlayer-python.readthedocs.io/en/latest/api/managers/block/#SoftLayer.managers.block.BlockStorageManager.get_volume_details

```
>>> pprint.pprint(bsm.get_block_volume_details(277949934)
... )
{'activeTransactionCount': 1,
 'activeTransactions': [{'createDate': '2022-12-01T19:03:09-06:00',
                         'elapsedSeconds': 1959,
                         'guestId': '',
                         'hardwareId': '',
                         'id': 302550242,
                         'modifyDate': '2022-12-01T19:03:10-06:00',
                         'statusChangeDate': '2022-12-01T19:03:09-06:00',
                         'transactionStatus': {'friendlyName': 'This is a '
                                                               'buffer time in '
                                                               'which the '
                                                               'customer may '
                                                               'cancel the '
                                                               'server'}}],

以下略
```

今回は、activeTransactionCount が 0 のものだけを削除対象にします。

# 完成したスクリプト

下記が完成したスクリプトです。
念のため 「cancel_volume」はコメントアウトしてあります。
確認後、コメントアウトを外して実行すると、一括削除されます。
対象の一覧表だけが欲しい場合、下から 2 行目の print 文もコメントアウトしてください。

```
import SoftLayer
client = SoftLayer.create_client_from_env()
bsm = SoftLayer.managers.block.BlockStorageManager(client)
bs_list=bsm.list_block_volumes(datacenter=None, username=None, storage_type=None, order=None)
for bs in bs_list:
  allowed = bsm.get_volume_access_list(bs['id'])
  details = bsm.get_block_volume_details(bs['id'])
  if ((allowed['allowedHardware'] == []) and
      (allowed['allowedIpAddresses'] == []) and
      (allowed['allowedSubnets'] == []) and
      (allowed['allowedVirtualGuests'] == []) and
      (details['activeTransactionCount'] ==0) ) :
     print(str(bs['id']) + ':' + str(bs['username']))
     print(str(allowed) + ' : ' + str(details['activeTransactionCount']))
#     bsm.cancel_volume(bs['id'], reason='No longer needed', immediate=False)
```

利用には、実行前に Username と API Key を環境変数にセットする必要があります。

```
export SL_USERNAME=YOUR_USERNAME
export SL_API_KEY=YOUR_API_KEY
```