vboxapiでVirtualBoxをPythonで操作その3

対象: VirtualBox-3.1, Python-2.6

前回、前々回の続きです。
VMを起動したり電源をブチっと切ってみたり、一時停止したりしてみます。これらの操作はIMachineのメソッドでははありません。m.pause() とかできたら簡単そうなのですが。
まず、VMがどのような状態を持つのかを確認しておきましょう。

VMが持つ状態

ドキュメントのenum MachineStateに状態遷移図がありますので、以下に引用します。

(略)
+-> PoweredOff --+-->[powerUp()]--> Starting --+      | +-----[resume()]-----+
|                |                             |      | V                    |
|   Aborted -----+                             +--> Running --[pause()]--> Paused
|                                              |      ^ |                   ^ |
(略)

非常にワクワクする図ですね。

VMの起動

ドキュメント通りにpowerUp()で起動するとなぜか失敗してしまいますので、前回ちらりと書いたように、VMの起動はopenRemoteSessionでRemoteSessionを開く方法を使います。
ドキュメントと違いますが、vboxshell.pyもopenRemoteSessionでやっていますので気にしないようにします。
なお、今回のコードからは状態チェックは省略しています。

import vboxapi

mid = '4f2f****-...'

vbm = vboxapi.VirtualBoxManager(None, None)
vbox = vbm.vbox

sessionManager = vboxapi.SessionManager(vbm)
session = sessionManager.getSessionObject(vbox)

progress = vbox.openRemoteSession(session, mid, "vrdp", "")
while not progress.completed:
    print "p:", progress.percent

print "result:", progress.resultCode
session.close()

時間がかかる処理はIProgressを返しますので、進捗や残り時間を表示することができます。

p: 0
...(略)
p: 0
p: 100
result: 0

すぐ終了してしまいましたが、起動処理が始まりました。
なお、VMはRunningになりますが、VM上のOSは起動プロセスの真っ最中です。

VMの電源オフ

電源オフは即電源を切ることです。実際のハードウェアで電源ボタンの長押しをした場合や、電源ケーブルを抜いた操作に近いです。
このテストをする前に、動作状態のSnapShotを取っておくことをお勧めします。

import vboxapi

mid = '4f2f****-...'

vbm = vboxapi.VirtualBoxManager(None, None)
vbox = vbm.vbox

sessionManager = vboxapi.SessionManager(vbm)
session = sessionManager.getSessionObject(vbox)
vbox.openExistingSession(session, mid)

progress = session.console.powerDown()
while not progress.completed:
    print "p:", progress.percent

session.close()

多くの操作は、session.consoleでIConsoleオブジェクトを取得し、そこから操作します。冒頭の失敗するpowerUp()もIConsoleのメソッドです。
実行結果です(途中の行は随時省略)。percentを表示しながら電源が切れました。

p: 28
p: 42
p: 70
p: 84

なお、今時のPCは電源ボタンを押すと、wikipedia:ACPIのPowerOff信号が飛ぶようになっています。そちらの処理をしたい場合は、IConsole.powerButtonを使います。

pause, resume (一時停止、再開)

電源Offとほぼ同じ手順です。一時停止は一瞬で完了するためかIProgressを返しません。

import vboxapi

mid = '4f2f****-...'

vbm = vboxapi.VirtualBoxManager(None, None)
vbox = vbm.vbox

sessionManager = vboxapi.SessionManager(vbm)
session = sessionManager.getSessionObject(vbox)
vbox.openExistingSession(session, mid)

session.console.pause()
# session.console.resume() # resume()ならresume
session.close()

簡単ですね。

その他の操作

APIドキュメントのIConsoleのメソッド一覧、メンバ一覧を見るとどのようなことができるのかがわかります。
C++のメソッドの最終引数が「[retval] out IProgress progress」になっているものが、PythonではIProgressを返すメソッドとなっています。
powerButton, takeSnapshot など色々揃っていますので、これも見てワクテカしましょう。

マウスやキーボードを制御する

APIからVMのマウスやキーボードを操作できます。
VM上のOSの状態が取れないのが難点ですが、色々な可能性を秘めたAPIです。

このサンプルは、VM上のOSがWindowsで、OS起動が完了しており、デスクトップが表示された状態を想定しています。
スクリプトで、以下の操作をしますので、これらの操作をさせても問題ないか確認してください。

  • マウスを画面の左下に移動
  • 画面の左下でマウスの左ボタンを押して、放す(スタートメニュー表示)
  • キーボードのWin+Eを押して、放す(ファイルエクスプローラの起動)
  • Ctrl+Alt+Deleteを押して自動的に放す(タスクマネージャ表示)
import vboxapi

mid = '4f2f****-...'

vbm = vboxapi.VirtualBoxManager(None, None)
vbox = vbm.vbox

sessionManager = vboxapi.SessionManager(vbm)
session = sessionManager.getSessionObject(vbox)
vbox.openExistingSession(session, mid)

session.machine.VRDPServer.enabled = True
print session.machine.VRDPServer.ports
session.machine.saveSettings()

console = session.console

x = 1
y = console.display.height
dz = dw = 0
buttonState = 0b001 # 0b010 : middleButton, 0b100 : rightButton

console.mouse.putMouseEventAbsolute(x, y, dz, dw, 0)
console.mouse.putMouseEventAbsolute(x, y, dz, dw, buttonState)
console.mouse.putMouseEventAbsolute(x, y, dz, dw, 0)

pushLeftWin = [0xe0, 0x5b]
pushE = [0x12]
releaseLeftWin = [0xe0, 0xdb]
releaseE = [0x92]

console.keyboard.putScancodes(pushLeftWin + pushE)
console.keyboard.putScancodes(releaseLeftWin + releaseE)
console.keyboard.putCAD()

session.close()

ゲストOSがWindowsの方は、スタートメニューが開き、ファイルエクスプローラとタスクマネージャが開いたはずです。(GUIの画面を開いていない方は、途中でVRDPを有効化してポート番号を表示させましたので、そこにRDP接続してください。)

putScancodesで送信しているのは、スキャンコードというものです。
押したときの信号と放したときの信号が別々に定義されています。キーは押したら放しましょう。
putCADでは、一度にCtrl+Alt+Deleteを送信できます。(そして自動的に放すようです)

どうでもいい余談

サンプルをわかりやすくするために、x、y、buttonStateなどの変数を導入しました。

console.mouse.putMouseEventAbsolute(x, y, dz, dw, buttonState)

vboxapiは対応してませんが、Python風に記述ができたなら、以下のようになると思います。Pythonスクリプトの可読性はすごい。

console.mouse.putMouseEventAbsolute(x=1, y=console.display.height, dz=0, dw=0, buttonState=0b001)

ここまでのまとめ

  • MachineStateの状態遷移図はワクワクします。
  • 起動はopenRemoteSessionです。
  • IConsole.fooMethod()で状態を変更します。
  • 一部の状態変更メソッド呼び出しは、ブロックせず、IProgressを返してすぐに戻ります。
  • IConsole.keyboardとIConsole.mouseでマウスとキーボードを操作できます。

今後の予定

サンプルコードはこれで終わりにします。
次回はシリーズ最終回で、調査中に見つけた、vboxapiを利用したアプリやプロジェクトを紹介します。