Friday, November 5, 2010

Scritping the guest VM and OS in VirtualBox from python

I like Innotek/Sun/Oracle VirtualBox. I liked the GUI of it. I liked the VBoxManage command line utility to maintain and manage the guest machines on my host. But later, I wanted to make the guest OS what I want - wanted to script them!

I was looking for a usable solution how could I script the guest OS inside of the vm in VirtualBox and accidentally I found the automatically installed /usr/lib/virtualbox/vboxshell.py python script, which is also a pretty good Cisco IOS fashioned shell. Its power is the usability and the open API. You can find similar possibility in VBoxManage command, but this is python, and rather a shell.

Let's see an example how good is it.
$ /usr/lib/virtualbox/vboxshell.py
Running VirtualBox version 3.2.10
vbox> list
    Machine 'OpenSolaris' [e2efac1d-4cd6-40ec-c388-d33fbd3c93b2], machineState=Saved, sessionState=Closed
    Machine 'OpenIndiana' [e6434c9f-eef5-43fd-9e63-cac8bf23d1f9], machineState=PoweredOff, sessionState=Closed
    Machine 'ubuntu64' [ffdf6bd9-81b5-433b-be0d-b4b342e9fa76], machineState=PoweredOff, sessionState=Closed
vbox> start ubuntu64
And voilà the guest ubuntu just started after listing the virtual machines. Now, from version 3.2 VirtualBox it has a great feature (note, the help command always helps):
vbox> help typeGuest
    typeGuest: Type arbitrary text in guest: typeGuest Linux "^lls\n&UP;&BKSP;ess /etc/hosts\nq^c" 0.7 
So let's use it, and type the command of "date" here:
vbox> typeGuest ubuntu64 "date\n"
And you can check the started VM's screen, it executed the keystrokes. Now it's time to make a screenshot about this output:
vbox> help screenshot
    screenshot: Take VM screenshot to a file: screenshot Win /tmp/win.png 1024 768
vbox> screenshot ubuntu64 /tmp/bubuntu.png
Saving screenshot (720 x 400) screen 0 in /tmp/bubuntu.png...
vbox>
and here we go, we got a capture about our guest VM.

Now let's play with it, create a snapshot of the guest's state, then change it by a "date" command, try to restore the snapshot of a running machine (you got the error message), turn off (powedown) the vm and restore to the previous snapshot and start the vm:
vbox> snapshot help
Take snapshot:    snapshot vm take name
Restore snapshot: snapshot vm restore name
Merge snapshot:   snapshot vm merge name
vbox> snapshot ubuntu64 take 'Snapshot 3' 'blahblah'
vbox> typeGuest ubuntu64 "date\n"
vbox> snapshot ubuntu64 restore 'Snapshot 3'
0x80bb0002 (Cannot delete the current state of the running machine (machine state: Running))
vbox> powerdown ubuntu64
vbox> snapshot ubuntu64 restore 'Snapshot 3'
vbox> start ubuntu64
vbox>
Now you can see, you even can reach the shooting starson the sky. :) Why are these good for us? Because it's python, and you can use this shell as a primary, easy-going API, to script the VirtualBox. How? On the host, you will need:
toma@clamshell:~$ mkdir pybox
toma@clamshell:~$ cd pybox
toma@clamshell:~/pybox$ mkdir vboxshell
toma@clamshell:~/pybox$ cp /usr/lib/virtualbox/vboxshell.py vboxshell/__init__.py #making a library from python script
toma@clamshell:~/pybox$ cat - > tst.py
#!/usr/bin/env python

machine_name = 'ubuntu64'
snapshot_name = 'Snapshot 2'
type = 'gui' #'headless' | 'gui'
delay = 0.0001

from vboxshell import * #using our newborn library
from vboxapi import VirtualBoxManager
from time import sleep

style = None
g_virtualBoxManager = VirtualBoxManager(style, None)
ctx = {'global':g_virtualBoxManager,
       'mgr':g_virtualBoxManager.mgr,
       'vb':g_virtualBoxManager.vbox,
       'const':g_virtualBoxManager.constants,
       'remote':g_virtualBoxManager.remote,
       'type':g_virtualBoxManager.type,
       'run': lambda cmd,args: runCommandCb(ctx, cmd, args),
       'guestlambda': lambda id,guestLambda,args: runGuestCommandCb(ctx, id, guestLambda, args),
       'machById': lambda id: machById(ctx,id),
       'argsToMach': lambda args: argsToMach(ctx,args),
       'progressBar': lambda p: progressBar(ctx,p),
       'typeInGuest': typeInGuest,
       '_machlist': None,
       'prompt': g_prompt
       }
ctx['perf'] = ctx['global'].getPerfCollector(ctx['vb'])

#doing the things with guest vm:
snapshotCmd(ctx, ['snapshot', machine_name, 'restore', snapshot_name])
#if it's already started, let's move on:
try:
    startCmd(ctx, ['start', machine_name, type])
except Exception as e:
    print e
typeGuestCmd(ctx, ['typeGuest', machine_name, '\n', delay])
typeGuestCmd(ctx, ['typeGuest', machine_name, 'iptables -|p |i|n|p|u|t |a|c|c|e|p|t\n', delay])
typeGuestCmd(ctx, ['typeGuest', machine_name, 'iptables -|p |o|u|t|p|u|t |a|c|c|e|p|t\n', delay])
typeGuestCmd(ctx, ['typeGuest', machine_name, 'iptables -|f\n', delay])
typeGuestCmd(ctx, ['typeGuest', machine_name, 'iptables -|x\n', delay])
sleep(3)
screenshotCmd(ctx, ['screenshot', machine_name, '/tmp/vm.png'])
powerdownCmd(ctx, ['powerdown', machine_name])
toma@clamshell:~/pybox$ chmod 755 tst.py
toma@clamshell:~/pybox$
 A few comments, the machine_name is trivial - it's the guest VM's name. The snapshot_name, what we'd like to restore all the time, it's already existing for me in this example. The type how we start the VM, it can be either gui or headless, where the last one can be useful if we'd like to have no graphics output of the running guest. And the last parameter, the delay, is the typing speed. It's pretty low latency. And what is that "|f|o|o"? read as shifted: "FOO". The API is using capitals for special keys such as "SHIFT" so we need to use this kind of escaping.
The g_virtualBoxManager and ctx responsible to get the VirtualBox' context, it's borrowed from the main function of the vboxshell to get the things working.

Now let's run it and see what it does:
$ ./tst.py
0x80bb0002 (Cannot delete the current state of the running machine (machine state: Running))
0x80bb0007 (A session for the machine 'ubuntu64' is currently open (or being opened or closed))
Saving screenshot (720 x 400) screen 0 in /tmp/vm.png...
It is its output, which is not much, but in the background it did:
  • snapshotCmd(ctx, ['snapshot', machine_name, 'restore', snapshot_name]) # restoring the snapshot
  • startCmd(ctx, ['start', machine_name, type]) # starting the machine, but if we got exception (that's why we have try/except) if the machine is already running, just go on
  • typeGuestCmd(ctx, ['typeGuest', machine_name, '\n', delay]) # press enter
    typeGuestCmd(ctx, ['typeGuest', machine_name, 'iptables -|p |i|n|p|u|t |a|c|c|e|p|t\n', delay]) #type "iptables -P INPUT ACCEPT"
    [...]
  • sleep(3) #gues what, 3 secs
  • screenshotCmd(ctx, ['screenshot', machine_name, '/tmp/vm.png']) #making a screen capture
  • powerdownCmd(ctx, ['powerdown', machine_name]) #turning off the guest vm
So that's so easy to script VirtualBox 3.2 from python, I hope you found this article handy. There are so many possibility (including moving the mouse, click, so I recommend to check the VirtualBox SDK document for further information.
Best luck!

No comments:

Post a Comment