PythonからVirtualBoxを操作する (セットアップ~VM電源操作)

仮想マシン環境と言えば色々ありますが、個人的にはVirtualBoxがPC環境でも使いやすいと思っています。安定性や品質、機能面では他の仮想化ソフトに道を譲りますが、開発用途では十分な印象があります。
何台か運用しているうち、CLIやスクリプトからも扱いたいと思い、PythonでのAPI連携を試してみました。

VirtualBoxが標準で提供しているAPI、vboxapiはあまり積極的にはエンハンスされていないようで、Pythonから使うのはひと工夫必要なようです。

歴史上の経緯で若干制限もあるため、最初からサードパーティ製のものを使うという選択肢もありますが、せっかくなので標準のものもちょっと調べてみました。

今回のサンプルは最後のほうで掲載します。環境はWindows10 Pro, Python2.7, VirtualBox 5.2 (一応6.0でも確認済)です。

もくじ:

1. vbox APIクライアントのセットアップ

基盤となるVirtualBox APIはCOM (XPCOM)で実装されており、様々な言語から制御できるようになっているようです。
SDKとしてPython向けのバインディングが用意されているので、マニュアルでCOMインタフェースの使い方を調べればPythonでもおおよそ同じ操作を実装することができます。

また、VirtualBoxではSOAP経由で同じインタフェースを公開しているRPCが提供されているので、VirtualBox(ハイパバイザ)がインストールされていないリモートのマシンからもAPIの呼び出しが可能です。

ローカルとリモートの関係を図示するとこんな感じです。

1.1 Python用クライアント環境のインストール

Python2.x系のみサポートなので、システムを汚したくなければvirtualenv,pyenvなどの仮想環境を使ったほうが良いと思います。
私が試した環境ではAnaconda (Miniconda)が導入されていましたので、condaコマンドで仮想環境を作ることにしました。
仮想環境の名前{env name}は何か適当に読み替えて下さい。

VirtualBoxがローカルにインストールされている環境ではSDKのインストーラもデフォルトで同梱されています。バージョンを気にしたくない場合は素直に同梱されているものを使うと良いと思います。
Windowsの場合、デフォルトでは下記のような感じです。

あるいは、VirtualBoxがインストールされていない環境なら(もちろん、されている環境でも)pip経由で入手できます。もちろんPython2(pip2)系でないとエラーになります。

Windows環境の場合はその他(COM操作関連)に依存パッケージpywin32(またはpypiwin32)が必要です。

ローカルで利用するのみ(つまりVirtualBoxホストで使う場合)であればここでインストール作業は終了です。
リモートで利用する場合はさらに必要な依存関係があります。

1.2 リモート管理用クライアント環境のインストール

リモートでAPIを扱うには、SOAPクライアントが必要になります。SDK(のサンプル)ではちょっと古いモジュールを使っているので、そのままでは使えません。

SOAPを扱うためのZSIモジュールがそもそも古いですが、ZSIが依存しているPyXMLも既にサポートが停止されているようです。
そのまま頑張るなら、どこぞから拾ってきたPyXMLをオフラインインストールし、さらにZSIをpip経由で導入します。

そもそも標準のSDKに頼る理由がなければ、SOAPクライアントをzeepなどモダンなモジュールで置き換えたRemote VirtualBox (https://github.com/ilyaglow/remote-virtualbox) などサードパーティ製のものを使うのも手です。インタフェース定義(WSDL)もSDKに同梱されているので自前でSOAPの手続きを実装することも一応可能なようです。
さらにSOAPを使う必要性も感じなければ、リモート呼び出し部分のインタフェースは自作する、というやり方でもいいかもしれません。

ここではとりあえずSDKのサンプルに準拠してみます。
PyXMLをWindowsにインストールするには、VC++コンパイラ(vcpython27)が必要です。Microsoftのサイトから入手してインストールします。
https://www.microsoft.com/en-us/download/details.aspx?id=44266

PyXMLは既にPyPIから削除されているので、Tarアーカイブをネットから入手します。現時点ではSourceForgeで配布していました。
https://sourceforge.net/projects/pyxml/files/

仮想環境でインストールします。

一部のリモート呼び出し用のバインディングは上記のvbox-sdkパッケージに含まれていないようなので別途設置が必要です。
公式サイトからSDK(ZIPアーカイブ)を入手し、個別にパスを通します。condaのような仮想環境を使っている場合はvboxapiが入っている場所にコピーしてしまうのが簡単です。
より具体的には、vboxapi.__file__が指しているパスと同じ階層ですね。

公式サイトから入手したzipアーカイブを解凍し、sdk/bindings/webservice/python/libのパスにあるスクリプト(VirtualBox_client.pyVirtualBox_types.pyVirtualBox_wrappers.py)を上記のvboxapiと同じ場所にコピーします。

1.3 リモートアクセス用Webサーバのセットアップ

ローカルで実行する際には不要ですが、リモートから操作する場合にはVirtualBoxに同梱のWebサーバVBoxWebSrvを使います。

1). Windows編

Windowsの場合は、VirtualBoxがインストールされたディレクトリにあるVBoxWebSrv.exeで、リモートからの通信を受け付けるために起動しておきます。
実行には管理者権限が必要ですので、管理者として起動したコマンドプロンプトまたはPowershellから実行するなどします。リモートからのアクセスを受け付けるには、明示的にホスト名(またはIPアドレス){host name}を指定します。

使用している場合はファイヤウォールを設定します。Listenポートは変えられますがデフォルトでは上記の起動オプションのように18083番(http, TCP-in)です。

終了する際にはタスクキルではなくCtrl+Cを使ったほうが良いです。リモートから設定変更中にキルすると、VMがabort状態になったりします。

2). Linux編

Linuxの場合、例えばUbuntuでは普通にaptを使って(例によって古いバージョンですが)VirtualBoxをインストールすることができ、Webサーバもサービスとしてセットアップされます。
ちなみにVirtualBoxをsudo apt installでインストールしようとすると失敗することがあるので、rootにスイッチして(sudo su -)aptインストールしたほうが無難です。

せっかくなのでサービスとしてセットアップされたものをちょっと修正して使うことにしました。起動オプションの指定は上記のWindowsの場合と同じで、ExecStartのところに追加します。

ファイヤウォールを使っている場合は設定しておきます。

上記のようにWebサーバは管理者アカウントで起動されるので、GUIを使いたい際にもsudoする必要があります。

1.4 VirtualBoxManager

実際のクライアント用スクリプトでは、ローカルでもリモートでも、基本的にはVirtualBoxManagerオブジェクトを使って操作していきます。
このVirtualBoxManagerによって隠蔽されるので、後で呼び出すメソッドはローカル、リモートに関わらず同じです。

リモートで操作する場合には第一引数に"WEBSERVICE"をセットします。URLは普通にhttp/httpsの、VBoxWebSrvの起動オプションに使用したホスト名とポートの組合せになります。
認証はデフォルトではVBoxWebSrvを起動しているのと同じものです。Windowsならログイン中のアカウント、Linuxならプロセスを起動している(PAM)アカウントです。
https/SSL通信や外部認証の利用など、標準で他のカスタマイズもサポートされています。

ローカル/リモートともに、VirtualBoxへの操作は個別にセッション的なものを張る仕様になっていますが、複数生成されても暗黙的には同期されません。つまり、Pythonから呼び出したセッションと、例えば普通のGUIとはそのままでは見かけのVMの状態が一致しないこともあります。設定系の操作のためにロック機構が用意されているので実質的に不整合になることが無いように設計されていますが、明示的に同期させるか、単一のセッションのみ使用するようにしたほうが良いです。

また、VMを対象とする操作に関してはVirtualBoxインスタンスを取得する必要があります。

2. VMの操作

ここでは基本的と思われる操作について記載していきます。

2.1 VM一覧の取得

VirtualBoxManager.getArray()メソッドに'machines'を指定すると、VMのリストに相当するものを取得することができます。
リストの各要素はIMachineインタフェースになっており、VMの属性や設定値を表すプロパティを参照できます。

サンプルでは特定のプロパティを文字列にキャストして表示させる処理を実装しており、実行するとこんな感じの結果になると思います。

2.2 VM(ゲスト)の起動

うまく起動に成功した場合、状態(state)が停止(PoweredOff)から実行中(FirstOnline)になります。
今回はホストにログインしてGUI操作するのが面倒という動機なので、ヘッドレスで起動させることにします。

サンプルの使用例はこんな感じ。VMの名前を指定して起動します。

まず、所望のVMへの参照を取得し、セッションを作成します。

ゲストの起動はIMachine.launchVMProcess()で実行でき、下記のようにヘッドレス(コンソールを表示しない)を指定することができます。起動状態への遷移にはある程度時間がかかるので、当該タスクの完了を待つ処理を入れます。

完了後にはセッション終了(closeMachineSession())を呼んだほうが無難です。

2.3 VMのシャットダウン

VMを終了する際には他のハイパバイザと同様に、いわゆる強制停止と、ゲストOSのシャットダウンとの2種類があります。
大抵のディストリビューションで問題なく動くので、もちろんACPIシャットダウンを使ったほうが良いでしょう。

サンプルの使用例はこんな感じ。VMの名前を指定してシャットダウンします。

内部的には、明示的にロックを取得した上で、停止IConsole.powerDown()、またはACPIシャットダウンIConsole.powerButton()を実行します。

3. サンプル

上記の操作の実装はこんな感じです。

別記事に続く。