OpenStack Cinderのorphan volumeのチェックについて(StoreVirtual VSA編)

# 概要

OpenStack を使ったクラウド基盤の故障試験を行っている場合等、使っているストレージ装置と Cinder のDBの間で不整合が発生することがあります。

具体的には、ストレージ装置側にのみ残っている古い Cinder volume や、DBにのみ存在して実体がストレージ装置からは消えてしまっているもののことです。このような volume を orphan volume と呼びますが、orphan volume があるかどうかを手動でチェックするのはたいへんな作業です。そこで、

1. admin ユーザで cinder list --all-tenants とした一覧
2. ストレージの中に、cinder が作ったものとして存在するものの一覧

の突合せを行い、いずれか一方にしか存在しない orphan 状態なものを抽出するツールを作ってみたので紹介したいと思います。

なお、OpenStack Cinder では、私が仕事でよく使うHPE製のストレージ装置としては StoreVirtual VSA と 3PAR がサポートされていますが、ここでは StoreVirtual VSAを例に使います。実は、3PAR用のものも作ったので、こちらも別途紹介した上で、まとめて整理した上で github で公開したいと考えています。

# チェックツール

ざっくり、以下のような処理を行っています。

1. ```cinder list --all-tenants``` 相当のAPIで一覧を取得
2. StoreVirtual VSAのAPIを使って一覧を取得
3. 差分をチェック

なお、1. については、最初は MySQL-python を使ってDBから直接抜き出していたのですが、これでははあんまりだと思い直して Cinder API を呼ぶようにしました。(笑)

もう一点、exception 処理はサボっています。まとめて公開するときには処理を追加します。


まず、ストレージ(HPE StoreVirtual VSA)からvolume一覧を取り出します。hpelefthandclient というのが python library ですが、名前から StoreVirtual VSA の前身が何かわかりますね。 :)

```Python
     1  from hpelefthandclient import client as lefthandclient
     2  from hpelefthandclient import exceptions as hpexceptions
     3  #
     4  # variables
     5  #
     6  lefthand_api_url = 'https://192.168.1.10:8081/lhos'
     7  lefthand_username = 'vsa-admin'
     8  lefthand_password = 'PASSWORD'
     9  lefthand_clustername = 'cluster-vsa'
    10  #
    11  # get volume list on StoreVirtual VSA
    12  #
    13  client =lefthandclient.HPELeftHandClient(lefthand_api_url,
    14                                           secure=False,
    15                                           suppress_ssl_warnings=True)
    16  client.login(lefthand_username, lefthand_password)
    17  volume_list= client.getVolumes(cluster=lefthand_clustername)
    18  client.logout()
    19
```
接続の準備をして、ログインして、ボリューム一覧を取り出して、ログアウトしているだけです。留意点としては、15行目の suppress_ssl_warnings に True をセットしておかないと、urllib3 が証明書の確認ができない旨のメッセージを出します。(このパラメータ、[マニュアル](http://pythonhosted.org/python-lefthandclient/api/hpelefthandclient/client.html#hpelefthandclient.client.HPELeftHandClient)に説明がなくてソースを見ないとわからないという :()

取り出したボリューム一覧から volumeのuuid だけ取り出して、Python の set 型に格納しておきます。set 型を使う理由は後でわかります。
なお、23行目で謎の文字列置換をしていますが、StoreVirtual VSAの場合は  'volume-' + volume_uuid という名前で内部的に保持しているため、prefix を削除しています。(余談ですが、3PARの場合は事情により、さらに別の名前付け規則に従います)

```Python
    20  # Parse result of getVolumes() and create a set of volume uuids from it.
    21  vsa_volume_set = set()
    22  for vsav  in volume_list['members']:
    23          vol = vsav['name'].replace('volume-', '')
    24          vsa_volume_set.add(vol)
```
以下が Cinder APIを呼び出す処理です。

```Python
    25  #
    26  # Retrieve volume list using Cinder API
    27  #
    28  from keystoneauth1.identity import v3
    29  from keystoneauth1 import session
    30  from keystoneclient.v3 import client
    31  from cinderclient import client as cinderclient
    32  from cinderclient.v2.volumes import Volume
    33  from cinderclient import base
    34  #
    35  os_auth_url= 'https://192.168.1.11:5000/v3'
    36  os_username = 'admin'
    37  os_password = 'PASSWORD'
    38  os_project_name = 'admin'
    39  os_project_domain_name = 'Default'
    40  os_user_domain_name = 'Default'
    41  os_endpoint_type='adminURL'
    42  os_volume_api_version='2.0'
    43  cert_verify=False
    44  #
```
Keystone v3 APIを使って session を作ります。(ここでは、project scope で認証を行っています)

```Python
    45  auth = v3.Password(auth_url=os_auth_url,
    46                     username=os_username,
    47                     password=os_password,
    48                     project_name=os_project_name,
    49                     project_domain_name=os_project_domain_name,
    50                     user_domain_name=os_user_domain_name)
    51  sess = session.Session(auth=auth, verify=cert_verify)
```
次に、Cinder v2 APIを使って volume 一覧を取り出します。CLIでは、```cinder list --all-tenants``` 相当です。

```Python
    52  cinder = cinderclient.Client(os_volume_api_version,
    53                               session=sess,
    54                               endpoint_type=os_endpoint_type)
    55  cinder_volumes = cinder.volumes.list(search_opts={'all_tenants': True})
    56
```
取り出した一覧を処理して、同じく Python の set 型に格納しておきます。

```Python
    57  # Parse result of cinder list --all-tenants equivalent API,
    58  # and create a set of volume uuids from it.
    59  cinder_volume_set = set()
    60  for v in cinder_volumes:
    61          cinder_volume_set.add(base.getid(v))
```

62行目以下は、VSAから取り出した volume id 一覧と、Cinder APIを使って取り出した volume id をもとに、いずれの一覧にも共通に含まれるもの(正常)と、ストレージ(VSA)上にしか存在しないもの(orphan)と、DB上にしか存在しないもの(orphan)を取り出しています。ここでは、Python の集合演算を使って簡単に処理しています。(それぞれ 69, 78, 87行目)このために、さきほど set 型で格納しておいたのです。

```Python
    62  #
    63  # output
    64  #
    65  separator = '-----------------------------------------------------------------'
    66  #
    67  #
    68  print separator
    69  volumes_common = vsa_volume_set.intersection(cinder_volume_set)
    70
    71  print 'Consistent (Exists in both VSA and Cinder DB). #volumes = ', len(volumes_common)
    72  for k in volumes_common:
    73          print k
    74  print ''
    75  #
    76  #
    77  print separator
    78  a= vsa_volume_set.difference(cinder_volume_set)
    79  print 'Orphan (Exists only in VSA). #volumes = ', len(a)
    80  for k in a:
    81          print k
    82  print ''
    83  #
    84  #
    85  #
    86  print separator
    87  b = cinder_volume_set.difference(vsa_volume_set)
    88  print 'Orphan (Exists only in CinderDB). #volumes = ', len(b)
    89  for k in b:
    90          print k
```

# 準備

## 動作環境について

前述のコードをはりつけて試してみようという方のために、使い方に簡単に触れておきます。

pip で以下のパッケージをインストールしておいてください。virtualenv の中で行うのがよいでしょう。

hpelefthandclient
python-keystoneclient
python-cinderclient

なお、hpelefthandclient が cryptgraphy パッケージに依存しているのですが、Ubuntu Xenial 環境では、このために以下を入れておく必要があります。venv の外側で入れてしまえばよいでしょう。

build-essential
libssl-dev
python-dev 

さて、実際に動かしてみるにあたって、準備としてまず、orphan volume を人工的に作り出します。

## ストレージ側にのみ存在する orphan volume の準備

```
stack@helion-cp1-c1-m1-mgmt:~$ cinder create --display-name orphan1g 1
+--------------------------------+--------------------------------------+
|            Property            |                Value                 |
+--------------------------------+--------------------------------------+
|          attachments           |                  []                  |
|       availability_zone        |                 nova                 |
|            bootable            |                false                 |
|      consistencygroup_id       |                 None                 |
|           created_at           |      2017-03-07T14:10:55.000000      |
|          description           |                 None                 |
|           encrypted            |                False                 |
|               id               | 7145d472-4d3d-48ef-91ca-92394f16c8fd |
|            metadata            |                  {}                  |
|        migration_status        |                 None                 |
|          multiattach           |                False                 |
|              name              |               orphan1g               |
|     os-vol-host-attr:host      |      ha-volume-manager@vsa#vsa       |
| os-vol-mig-status-attr:migstat |                 None                 |
| os-vol-mig-status-attr:name_id |                 None                 |
|  os-vol-tenant-attr:tenant_id  |   04d2f57fbd66496282597c367722d39a   |
|       replication_status       |               disabled               |
|              size              |                  1                   |
|          snapshot_id           |                 None                 |
|          source_volid          |                 None                 |
|             status             |               creating               |
|           updated_at           |      2017-03-07T14:10:52.000000      |
|            user_id             |   ac561fb3e65149aeb3d4b909d4e1649e   |
|          volume_type           |                 vsa                  |
+--------------------------------+--------------------------------------+

stack@helion-cp1-c1-m1-mgmt:~$ cinder list
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
|                  ID                  |   Status  |   Name   | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
| 1199050e-e927-4746-adce-86824a1a4d39 | available |          |  10  |     vsa     |   true   |             |
| 37022f3c-3895-4fc7-92bf-6d5e58261a21 | available |   bfv    |  10  |     vsa     |   true   |             |
| 42fcf325-2606-40d7-af2e-1ce537b2d2a8 | available |          |  10  |     vsa     |   true   |             |
| 9b966933-22c3-4afa-a030-2b2a16d6ccab | available |   vol    |  1   |     vsa     |  false   |             |
| f4d62274-e3cb-49d1-b57e-b27e775e5b88 | available |          |  2   |     vsa     |   true   |             |
| 7145d472-4d3d-48ef-91ca-92394f16c8fd | available | orphan1g |  1   |     vsa     |  false   |             |
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
```

mysql の cinder DBに接続し、当該 volume の deleted フラグを操作します。

```
(中略)
mysql> update volumes set deleted=1  where id='7145d472-4d3d-48ef-91ca-92394f16c8fd' ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> quit
Bye

stack@helion-cp1-c1-m1-mgmt:~$ cinder  list
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
|                  ID                  |   Status  |   Name   | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
| 1199050e-e927-4746-adce-86824a1a4d39 | available |          |  10  |     vsa     |   true   |             |
| 37022f3c-3895-4fc7-92bf-6d5e58261a21 | available |   bfv    |  10  |     vsa     |   true   |             |
| 42fcf325-2606-40d7-af2e-1ce537b2d2a8 | available |          |  10  |     vsa     |   true   |             |
| 9b966933-22c3-4afa-a030-2b2a16d6ccab | available |   vol    |  1   |     vsa     |  false   |             |
| f4d62274-e3cb-49d1-b57e-b27e775e5b88 | available |          |  2   |     vsa     |   true   |             |
+--------------------------------------+-----------+----------+------+-------------+----------+-------------+
```
これで、今作った id='7145d472-4d3d-48ef-91ca-92394f16c8fd' (Name = orphan1g) の volume は見えなくなりました。

## DB側にのみ存在する orphan volume の準備

次に、もういちど mysql の cinder DBに接続し、削除済み volume volume の deleted フラグを操作して 0 に戻します。

```
mysql> select id, display_name from volumes where deleted!=0 order by created_at desc limit 2;
+--------------------------------------+--------------+
| id                                   | display_name |
+--------------------------------------+--------------+
| 7145d472-4d3d-48ef-91ca-92394f16c8fd | orphan1g     | ←さきほど実体があるにもかかわらず deleted=1 として orphan 状態にしてもの。
| 5e2c1eb1-6011-49b4-a553-f67745d62a67 | deleted1g    | ←これを使って、deleteされたものを無理やり復活させ、上とは逆の意味で orphan にします。
+--------------------------------------+--------------+
2 rows in set (0.00 sec)

mysql> update volumes set deleted=0  where id='5e2c1eb1-6011-49b4-a553-f67745d62a67';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

stack@helion-cp1-c1-m1-mgmt:~$ cinder list
+--------------------------------------+-----------+-----------+------+-------------+----------+-------------+
|                  ID                  |   Status  |    Name   | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+-----------+------+-------------+----------+-------------+
| 1199050e-e927-4746-adce-86824a1a4d39 | available |           |  10  |     vsa     |   true   |             |
| 37022f3c-3895-4fc7-92bf-6d5e58261a21 | available |    bfv    |  10  |     vsa     |   true   |             |
| 42fcf325-2606-40d7-af2e-1ce537b2d2a8 | available |           |  10  |     vsa     |   true   |             |
| 5e2c1eb1-6011-49b4-a553-f67745d62a67 |  deleted  | deleted1g |  1   |     vsa     |  false   |             |
↑ 今、deleted を 1 から 0 に戻したもの。status は操作していないので 'deleted' と表示されている。
| 9b966933-22c3-4afa-a030-2b2a16d6ccab | available |    vol    |  1   |     vsa     |  false   |             |
| f4d62274-e3cb-49d1-b57e-b27e775e5b88 | available |           |  2   |     vsa     |   true   |             |
+--------------------------------------+-----------+-----------+------+-------------+----------+-------------+
stack@helion-cp1-c1-m1-mgmt:~$
```

deleted1g という名前の volume が見えるようになりましたが、これには実体はありません(orphan)。


# 実行結果

それでは動かしてみましょう。うまく検出できるでしょうか？

```
(venv) stack@xenial:~/work$ python vsacheck.py
------------------------------------------------------------------------
Consistent (Exists in both VSA and Cinder DB). #volumes =  15  ← '--all-tenants'(相当API) で出しているので cinder list の結果よりも多い
f6db817e-4611-4347-8b3f-453e9a81a470
9cd172ea-74be-47b7-bd41-be1135c29f5a
a0894780-394e-4f23-b954-db2b5d5501bc
bdde8d74-c4dc-4e65-9ad8-189ca9463e9c
f4d62274-e3cb-49d1-b57e-b27e775e5b88
1199050e-e927-4746-adce-86824a1a4d39
5d88e565-8ac6-49fd-9125-4745246ecd68
6648e793-2e9a-4a92-a08e-0cecb346bb53
1bf6ee0e-dc33-41b7-b42d-e7757da83a8e
42fcf325-2606-40d7-af2e-1ce537b2d2a8
71bf9a91-74ae-4f78-a676-40d092c5675f
a3f57936-07f1-4a0c-8c95-2c410d27217d
9b966933-22c3-4afa-a030-2b2a16d6ccab
37022f3c-3895-4fc7-92bf-6d5e58261a21
abf229ad-9bf0-4de0-a79b-d10e0d31dafa

------------------------------------------------------------------------
Orphan (Exists only in VSA). #volumes =  1     ← ストレージ(VSA)側にのみ存在して、DB上は存在しないか削除済になっているもの。
7145d472-4d3d-48ef-91ca-92394f16c8fd           ← orphan1g に対応。DBを操作して、未削除の volume の deleted フラグだけ1に書き換えたもの。

------------------------------------------------------------------------
Orphan (Exists only in Cinder DB). #volumes =  1 ← DB側にのみ存在して、ストレージ(VSA)には既に存在しないもの。
5e2c1eb1-6011-49b4-a553-f67745d62a67             ← deleted1g に対応。削除済の volume の delted フラグだけ0に戻したもの。
(venv) stack@xenial:~/work$
```

以上のように、準備で作った2つのorphan volumeが検出できていることがわかります。

# 議論

ところで、ここで紹介した手法だけで、ごみ掃除処理として十分でしょうか？

*  volume export
    *  通常のストレージ装置では、自分の中に作成した volume を、保護のために特定のホストからしかアクセスできないような制御を行います。(volume export 処理) Cinder にはこのための処理も入っているのですが、orphan 状態になるのは、volume だけでなく export が orphan になることもあります。
    * 特に、volume export のごみが残っている場合、当該ボリュームを cinder delete 削除しようとしても失敗することもあります。実運用上は、こういう対処を考える必要もあります。
* snapshot
    * orphan volume には、ストレージ装置上で snapshot が残っている可能性もあります。こういう場合は snapshot の掃除も考える必要があります。

# まとめ

* OpenStack Cinder の backend としてストレージ装置(ここでは、HPE StoreVirtual VSA)を運用している場合に、orphan volume をチェックする手法の１つを紹介しました。
* HPE のストレージとしては　StoreVirtual VSA と 3PAR が Cinder でサポートされており、いずれもREST API経由で操作できます。それぞれ python binding が公開されており、cinder driver でも使われています。
* ソースは3PAR用とあわあせて、まとめて別途公開します。
* 異常ケースの後片付けはパターンが多く、いろいろ大変です :(

# リンク集

* HPE StoreVirtual VSA用python library
    * http://pythonhosted.org/python-lefthandclient/api/hpelefthandclient/
* python-keystoneclient
    * https://pypi.python.org/pypi/python-keystoneclient/3.10.0
* python-cinderclient
    * https://pypi.python.org/pypi/python-cinderclient/1.11.0
* Python の集合演算関連
    * https://docs.python.org/2/library/stdtypes.html#set