Python library for ZFS (pyzfs) のバグを修正する
本エントリは Solaris Advent Calendar 2016 の 22 日目です。
Python library for ZFS
libzfs の zfs_iter_*() 系 API 利用について、ソースツリーを網羅的に調べた際に、Python から ZFS を利用するためのライブラリが提供されているのに気付きました (ソースツリー上の格納先から、以下このライブラリを pyzfs と呼びます)。
OpenIndiana 環境であれば、/usr/lib/python2.6/vendor-packages 配下に zfs パッケージとしてインストールされており、システムデフォルトの Python 処理系から事前準備等無しで使用できる筈です。
しかし実際に使ってみると幾つか不具合があったので、これらを修正してみました。
修正版ソースは Bitbucket 上で公開しています。本ブログエントリに関するコードは pyzfs ディレクトリ配下にあります。
illumos 本家に正式に修正要求出さないとなぁ……
要素一覧取得が機能しない
pyzfs における ZFS 要素一覧取得を実際に使ってみると、正しい結果を得られないケースの方が多いです。
例えば、以下のような稼働環境で:
$ zfs list -d 1 -o name -H rpool rpool rpool/ROOT rpool/dump rpool/export rpool/swap $
pyzfs による "rpool" 直下の非スナップショット要素の一覧取得を行っても、空の結果とかだったりします。
$ python -i Python 2.6.4 (r264:75706, Nov 16 2013, 20:49:19) [C] on sunos5 Type "help", "copyright", "credits" or "license" for more information. >>> import zfs.ioctl >>> def iterfilesystems(basename): ... cookie = 0 ... while True: ... result = zfs.ioctl.next_dataset(basename, 0, cookie) ... (element, cookie, props) = result ... yield element ... >>> list(iterfilesystems('rpool')) [] >>>
あるいは、存在するスナップショットに対して:
$ zfs list -d 1 -t snapshot -o name -H rpool/ROOT/BE20161216-01 rpool/ROOT/BE20161216-01@install rpool/ROOT/BE20161216-01@2012-03-29-04:34:40 rpool/ROOT/BE20161216-01@2016-12-10-06:50:18 rpool/ROOT/BE20161216-01@2016-12-15-07:49:00 $
pyzfs だと一部しか認識されない、といった感じです。
>>> def itersnapshots(zfsname): ... cookie = 0 ... while True: ... result = zfs.ioctl.next_dataset(zfsname, 1, cookie) ... (element, cookie, props) = result ... yield element >>> >>> print '\n'.join(itersnapshots('rpool/ROOT/BE20161216-01')) rpool/ROOT/BE20161216-01@2012-03-29-04:34:40 rpool/ROOT/BE20161216-01@2016-12-15-07:49:00 >>>
zfs.ioctl モジュールは pyzfs 中唯一の C 実装なので、以下のような DTrace スクリプトを使って ioctl() の出入りを見張ってみました。
fbt:zfs:zfs_ioc_snapshot_list_next:entry, fbt:zfs:zfs_ioc_dataset_list_next:entry { self->zc = (zfs_cmd_t*)(args[0]); printf("%s entry\n", probefunc); printf(" zc_name = %s\n", self->zc->zc_name); printf(" zc_nvlist_dst_size = %llx\n", self->zc->zc_nvlist_dst_size); printf(" zc_cookie = %llx\n", self->zc->zc_cookie); } fbt:zfs:zfs_ioc_snapshot_list_next:return, fbt:zfs:zfs_ioc_dataset_list_next:return { printf("%s return = %d\n", probefunc, args[1]); printf(" zc_name = %s\n", self->zc->zc_name); printf(" zc_nvlist_dst_size = %llx\n", self->zc->zc_nvlist_dst_size); printf(" zc_cookie = %llx\n", self->zc->zc_cookie); }
「rpool 配下は空」となる場合の ioctl() の出入りを見張ってみると:
$ pfexec dtrace -s trace_zfs_ioctl.d -c "python pyzfs/sample/py_get_filesystems.py rpool" -q zfs_ioc_dataset_list_next entry zc_name = rpool zc_nvlist_dst_size = 800 zc_cookie = 0 zfs_ioc_dataset_list_next return = 12 ※ ENOMEM zc_name = rpool/ROOT (*1) zc_nvlist_dst_size = b08 zc_cookie = 17478891 (*1) zfs_ioc_dataset_list_next entry zc_name = rpool/ROOT (*2) zc_nvlist_dst_size = b08 zc_cookie = 17478891 (*2) zfs_ioc_dataset_list_next return = 12 ※ ENOMEM zc_name = rpool/ROOT/BE20161216-01 (*3) zc_nvlist_dst_size = c90 zc_cookie = 1ba17752 zfs_ioc_dataset_list_next entry zc_name = rpool/ROOT/BE20161216-01 zc_nvlist_dst_size = c90 zc_cookie = 1ba17752 zfs_ioc_dataset_list_next return = 3 ※ ESRCH ⇒ 走査打ち切り通知 zc_name = rpool/ROOT/BE20161216-01/ zc_nvlist_dst_size = c90 zc_cookie = 1ba17752 $
要するに:
- (*1) ENOMEM 終了でも、zc_name や zc_cookie フィールドは改変済みなのに、
- (*2) ENOMEM 時の再試行で、フィールド値が改変されたままなので、
- (*3) ENOMEM 後の再実行での返却値が、想定外のものになっている
という状況な模様です。
zfs.ioctl モジュールの C 実装を見ると、確かに ENOME 時の zc_name や zc_cookie 復旧処理が抜けていました。libzfs の zfs_iter_*() 系 API の ioctl() 呼び出しを行う内部実装 zfs_do_list_ioctl() では、この辺の復旧処理をキチンとやっています。
復旧処理の抜けを修正することで、zfs.ioctl.next_dataset() が正しく機能するようになります。
Bitbucket リポジトリ中には、各ユーザの権限範囲内でローカルに修正版ライブラリをビルドしやすいように、setup.py を置いてありますので、pyzfs/README.rst での説明を参照してビルドしてください。
なお、ioctl.c のコンパイルには、ソースツリーでしか提供されない内部 C ヘッダが必要なので、illumos-gate 自体の入手は必要です (諸々のビルド前事前準備等の作業は不要)。
また、ビルドに使用する illumos-gate ソースツリーと、実稼働環境との間で、各種定義の値が食い違うと、実行時に segment fault 等が発生します。
定義の食い違いを確認するには、上記 README.rst 中の "Trouble shooting" での説明に従い、pyzfs/checktools で make を実行してみてください。なお、各種定義の食い違いが検出されても make の実行自体は成功しますので、実行結果出力の評価は、各自で行う必要があります。
Dataset.__repr__() で AttributeError が発生
pyzfs では、ファイルシステムやスナップショットを抽象化するクラスとして、zfs.dataset.Dataset クラスが提供されています。
しかし、このクラスのインスタンスを文字列化しようとすると、想定外の AttributeError が発生してしまいます。
$ python -i Python 2.6.4 (r264:75706, Nov 16 2013, 20:49:19) [C] on sunos5 Type "help", "copyright", "credits" or "license" for more information. >>> import zfs.dataset >>> rpool = zfs.dataset.Dataset('rpool') >>> rpool Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.6/vendor-packages/zfs/util.py", line 51, in default_repr str += " %s: %r" % (v, getattr(self, v)) AttributeError: 'Dataset' object has no attribute '__props' >>>
この問題は、実は ZFS とは全然無関係で、以下の組み合わせによって発生しているものです。
- "__foo" 形式の属性名は Python が自動的に "_classname__foo" に改名する
- 文字列化処理はとにかく "__foo" という属性名を参照しようとする
文字列化の際に、"__" で始まる属性は無視することで、Dataset クラスの文字列化が、正しく機能するようになります。