nova-hooks によるイベントハンドリング
こんにちは、インフラストラクチャ本部の大山裕泰です。今日は (恐らく) あんまり知られていない OpenStack のマイナー機能を紹介したいと思います。今日の内容は nova-hooks を用いたイベントハンドリングになります。これの仕組みと実装、そして実際に動作させた際にどうなるのかまで見てゆきます。尚、今回利用した OpenStack の環境は Juno (2014.2.1-0ubuntu1~cloud0) になります。
nova-hooks ってなに?
nova-hooks によって、OpenStack 本体の nova.hooks がデコレートされたメソッドに対して、任意の処理を関連づけることができます。具体的には、nova.hooks がデコレートされたメソッドに紐づいたユーザ定義の Python パッケージをインストールすることで、nova-hooks は当該メソッドが呼ばれる前と後にユーザ定義の処理を実行します。この辺りの仕組みはとても洗練されており、後で具体的なコードを交えながら解説してゆきます。
何がうれしいの?
現在 nova-hooks がデコレートされているメソッドは、nova-api がインスタンスの作成時に実行するメソッド (create) と、同じく nova-api がインスタンスの削除要求を受け取った際に実行する内部メソッド (_delete_instance) の2つが存在します。またこの他にも nova-network においてインスタンスのネットワークが有効になった際に呼び出されるメソッド (update_instance_cache_with_nw_info) もあります。
例えば「インスタンスが作成 (削除) された際に、課金システムと連携して当該インスタンスを利用したユーザへ請求を更新するなど、インスタンスの作成・削除に連動して外部のシステムやサービスと連携する際に便利かと思います。
どんなふうに使えるの?
では実際にどんな風に使えて、どんな事が出きるのかをコードを交えながら見てゆきます。ここでは例としてインスタンス作成時に呼び出されるメソッドに対して、「入力情報のログを吐き出す」処理を行うクラスを関連付けます。
nova-api に対してインスタンス作成要求が到着すると、以下 nova.compute.api.API の create メソッドが呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class API(base.Base): """API for interacting with the compute manager.""" ... @hooks.add_hook("create_instance") def create(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, min_count=None, max_count=None, display_name=None, display_description=None, key_name=None, key_data=None, security_group=None, availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, block_device_mapping=None, access_ip_v4=None, access_ip_v6=None, requested_networks=None, config_drive=None, auto_disk_config=None, scheduler_hints=None, legacy_bdm=True, shutdown_terminate=False, check_server_group_quota=False): |
create メソッドにデコレータ式 add_hook が適応されています。これが指定されている全てのメソッドに対し、後述する方法によってユーザ定義のクラスを紐付けることができます。
add_hook に渡されている引数 "create_instance" は、nova-hooks に対してユーザ定義のクラスをこの create メソッドに紐付けるための識別子になります。
それでは、ユーザ定義クラスとユーザ定義クラスを nova-hooks に登録する処理について見てゆきます。
ユーザ定義クラスの作成
まずはユーザ定義クラスの実装について見てゆきます。以下が nova.compute.api.API クラスの create メソッドをフックするユーザ定義クラス (MyCreateHook) になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/python from nova.openstack.common import log as logging LOG = logging.getLogger(__name__) class MyCreateHook(object): """This is an example to experience nova-hooks""" def pre(self, *args, **kwargs): for arg in args: LOG.info("args: %s" % (str(arg))) for key, value in kwargs.items(): LOG.info("kwargs: (key, value) = (%s, %s)" % (key, str(value))) def post(self, *args, **kwargs): for arg in args: LOG.info("args: %s" % (str(arg))) for key, value in kwargs.items(): LOG.info("kwargs: (key, value) = (%s, %s)" % (key, str(value))) |
MyCreateHook には、nova.compute.api.API クラスの create メソッドが実行される前に呼び出される (pre) メソッドと実行後に呼び出される (post) メソッドの 2 つだけが定義されています。
各メソッドの (*args, **kwargs) という引数の形式は python で汎用的な可変長引数を取るメソッドを実装する時の定石のようで、呼び出し側でリスト形式とハッシュ形式で引数を渡した場合においても、それぞれ args と kwargs で受け取れるので統一的な表記で実装できるようになっています。
MyCreateHook の pre/post の各メソッドではそれぞれ、nova.openstack.common.log モジュール を使って引数を OpenStack のログに出力しています。
次にこれを登録し、インスタンス作成時に MyCreateHook で処理を受け取れるところまでを見てゆきます。
ユーザ定義クラスの登録
nova-hooks に MyCreateHook クラスを登録するには python パッケージを作成し、パッケージのどのクラスを nova-hooks にデコレートされたメソッドに紐付けるかの設定をそれぞれ python のパッケージ管理機構 setuptools を使って行います。
以下では、MyCreateHook クラスを含む nova_hooks_example パッケージを作成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python import setuptools setuptools.setup( name="nova_hooks_example", packages=['myhook'], entry_points={ 'nova.hooks': [ 'create_instance=myhook.create:MyCreateHook' ] }, ) |
尚、ディレクトリツリーは以下のようになっています。
1 2 3 4 5 |
. ├── myhook │ ├── __init__.py │ └── create.py └── setup.py |
Python には、動的なサービスやプラグイン拡張の仕組み (https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins) を提供しており、setuptools では、entry_points パラメータによって、アプリケーションに対してコールバックルーチンを渡す事ができます。
nova-hooks では、上記のようにデコレータ式 add_hook の引数で指定した名前と、ユーザ定義クラス名をそれぞれ key と value に指定することで処理の紐付けを行っています。
動かしてみた
それでは nova_hooks_example パッケージを生成、及びインストールを実施します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
ohyama@openstack-controller:~/pypi$ sudo python setup.py install running install running bdist_egg running egg_info creating nova_hooks_example.egg-info writing nova_hooks_example.egg-info/PKG-INFO writing top-level names to nova_hooks_example.egg-info/top_level.txt writing dependency_links to nova_hooks_example.egg-info/dependency_links.txt writing entry points to nova_hooks_example.egg-info/entry_points.txt writing manifest file 'nova_hooks_example.egg-info/SOURCES.txt' reading manifest file 'nova_hooks_example.egg-info/SOURCES.txt' writing manifest file 'nova_hooks_example.egg-info/SOURCES.txt' installing library code to build/bdist.linux-x86_64/egg running install_lib running build_py creating build creating build/lib.linux-x86_64-2.7 creating build/lib.linux-x86_64-2.7/myhook copying myhook/__init__.py -> build/lib.linux-x86_64-2.7/myhook copying myhook/create.py -> build/lib.linux-x86_64-2.7/myhook creating build/bdist.linux-x86_64 creating build/bdist.linux-x86_64/egg creating build/bdist.linux-x86_64/egg/myhook copying build/lib.linux-x86_64-2.7/myhook/__init__.py -> build/bdist.linux-x86_64/egg/myhook copying build/lib.linux-x86_64-2.7/myhook/create.py -> build/bdist.linux-x86_64/egg/myhook byte-compiling build/bdist.linux-x86_64/egg/myhook/__init__.py to __init__.pyc byte-compiling build/bdist.linux-x86_64/egg/myhook/create.py to create.pyc creating build/bdist.linux-x86_64/egg/EGG-INFO copying nova_hooks_example.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO copying nova_hooks_example.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO copying nova_hooks_example.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO copying nova_hooks_example.egg-info/entry_points.txt -> build/bdist.linux-x86_64/egg/EGG-INFO copying nova_hooks_example.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO zip_safe flag not set; analyzing archive contents... creating dist creating 'dist/nova_hooks_example-0.0.0-py2.7.egg' and adding 'build/bdist.linux-x86_64/egg' to it removing 'build/bdist.linux-x86_64/egg' (and everything under it) Processing nova_hooks_example-0.0.0-py2.7.egg Removing /usr/local/lib/python2.7/dist-packages/nova_hooks_example-0.0.0-py2.7.egg Copying nova_hooks_example-0.0.0-py2.7.egg to /usr/local/lib/python2.7/dist-packages nova-hooks-example 0.0.0 is already the active version in easy-install.pth Installed /usr/local/lib/python2.7/dist-packages/nova_hooks_example-0.0.0-py2.7.egg Processing dependencies for nova-hooks-example==0.0.0 Finished processing dependencies for nova-hooks-example==0.0.0 ohyama@openstack-controller:~/pypi$ |
アンインストール作業は、単純にインストールしたパッケージファイル (/usr/local/lib/python2.7/dist-packages/nova_hooks_example-0.0.0-py2.7.egg) を削除する感じになります。
この状態で nova-api を再起動してインスタンスを作成すると以下の内容が nova-api のログ (/var/log/nova/nova-api.log) に出力されます。
終わりに
いかがだったでしょうか。MyCreateHook の pre/post メソッドによってインスタンス作成 (削除) に連動していろいろ出来そうな気がしてきたんじゃないでしょうか。
今後も OpenStack の小ネタをちょこちょことご紹介できればと思います。