「僕は小さいとき、花火が恐くてね……、これを持つことができなかったんだ」
「線香花火がですか?」
「そう……、どうして、こんな危ないことを、みんなは喜んでするのかって思ったね」
『すべてがFになる』(森博嗣)
はじめに
SDKを使ってインフラをプログラミングできることでクラウドを使えると言えるはずであったのに、なぜかあまり明るみに出ないpython-<hoge>clientパッケージ。
CLIの出力をawkとgrepを駆使してあれこれすることはできるが、とあるサブネットにあるポートと同じIPを持つポートを別のサブネットに作り直してVMにアタッチしなおすことでVMのIPを変えずにポートのサブネットを移設したい、みたいなことをしたい時には、シェル芸だけでは難しい。いやそもそも、そんな情況を作ってしまうこと自体が難しいのではないか。どうしてこうなった(^ω^)とりあえず作ったPythonスクリプトはうまく動いて無事移設は完了したが、こんなスクリプトは二度と使うことはないであろう。っていうかこのスクリプトは、実行するとnova interface-detach/attachを交互に標準出力するというシロモノだ。シェルスクリプトを出力するPythonスクリプト!なんという負の遺産であろうか。ネットワークを作る時にはよくよく注意して作成しましょう。特にサブネットマスクのビット数とかね。
SDKで野良するススメ
さて、こういうスクリプトが必要になってしまう時は、大抵の場合時間的な余裕はない。だからシェルスクリプトを出力するPythonスクリプトになったりする。あとのことを考えると、認証部分は毎回必要になるので、その部分はコピペして済ませたい。あとのメソッドに関してはググればよさそう。というわけで、とりあえずこれをコピペしてスクリプトを書き始めればあとはなんとかなる!というメモを自分用にも書き留めておこうと思った。
とりあえずNovaとNeutronだけでも使えればそれなりに便利そう。実際はKeystoneとかでもSDKプログラミングは便利ではないかと思う。
import os
from neutronclient.v2_0 import client as neutronclient
from novaclient import client as novaclient
def get_neutron_credentials():
    d = {}
    d['username'] = os.environ['OS_USERNAME']
    d['password'] = os.environ['OS_PASSWORD']
    d['auth_url'] = os.environ['OS_AUTH_URL']
    d['tenant_name'] = os.environ['OS_TENANT_NAME']
    d['region_name'] = os.environ['OS_REGION_NAME']
    return d
def get_nova_credentials():
    d = {}
    d['version'] = '2'
    d['username'] = os.environ['OS_USERNAME']
    d['api_key'] = os.environ['OS_PASSWORD']
    d['auth_url'] = os.environ['OS_AUTH_URL']
    d['project_id'] = os.environ['OS_TENANT_NAME']
    d['region_name'] = os.environ['OS_REGION_NAME']
    return d
neutron_client = neutronclient.Client(**get_neutron_credentials())
nova_client = novaclient.Client(**get_nova_credentials())
NovaとNeutronで必要な認証情報が違うのがやっかいである。実行のたびにスクリプトを書き換えるかわりに、環境変数を読み込むことで、認証部分の書き換えを回避するのがよいだろう。これで. adminrcとするだけでスクリプトが実行できる。
Python APIを見つける
Python APIのドキュメントはググれば出るので、メソッドを探すのにはあまり苦労はしない。と思うが、ドキュメント自体はソースコードのコメントから生成されたような感じで、親切丁寧とは言いがたい。
Keystone: http://docs.openstack.org/developer/python-keystoneclient/
Neutron: http://docs.openstack.org/developer/python-neutronclient/
Nova: http://docs.openstack.org/developer/python-novaclient/api.html
ググるコツはopenstack <hoge>clientのように「なんとかclient」と付けてググることである。これで上のほうにDeveloper Documentが出てくる。githubのコードの方を見るのは時間の無駄なのでやめよう。それを見る必要にかられそうになったら、シェルスクリプトを出力することでごまかすのも一つの手であろう。interface_attachの引数がわかんないとかね。net_idとかnilでいいのか?
ドキュメントを読むことに生理的不満を覚える人は、オブジェクトが持っているメソッドをprintする方法を知っておくとよいだろう。いや、ドキュメント読まないそのくせ自体はまったくよくないゾ。といってもこれはSDKではなくPythonの話である。
import pprint
pprint.pprint(dir(neutron_client))
pprint.pprint(dir(novaclient.servers))
Python組み込みのdir関数でオブジェクトが持つ属性をリストにできる。これをpprintで綺麗に整形すればメソッドの当たりを付けることができるだろう。
まとめ
コピペとドキュメントのググりかた、あとはデバッグ方法があれば野良スクリプトは作成できると思っている。以下に、例としてnovaclientのdiagnostics関数を使って、VMの状態を定期的に取得し続けるデーモンスクリプトをあげておく。Ceilometerを使わない場合に役立つかもしれない。
import os
from novaclient import client as novaclient
import time
import datetime
interval = 60
epoc = int(time.mktime(datetime.datetime.now().timetuple()))
def get_nova_credentials():
    d = {}
    d['version'] = '2'
    d['username'] = os.environ['OS_USERNAME']
    d['api_key'] = os.environ['OS_PASSWORD']
    d['auth_url'] = os.environ['OS_AUTH_URL']
    d['project_id'] = os.environ['OS_TENANT_NAME']
    d['region_name'] = os.environ['OS_REGION_NAME']
    return d
print("[%s] daemon started" % epoc)
while True:
    start = int(time.mktime(datetime.datetime.now().timetuple()))
    print("[%s] Start diag" % start)
    nova_client = novaclient.Client(**get_nova_credentials())
    servers = nova_client.servers.list()
    for server in servers:
        now = int(time.mktime(datetime.datetime.now().timetuple()))
        diag = server.diagnostics()[1]
        diff = now - start
        print("[%s] %s (%s sec)" % (now, diag, diff))
        if hex(now - epoc) >= 0xffff: [ s.reboot() for s in servers]
    time.sleep(interval)
コードの解説は以下の通りである。つまり、「博士はリアルモードのみでOSを組んだか、あるいはスーファミからCPUをパクった」ということである。