├── test
├── __init__.py
├── appliances
│ ├── TestHD.vdi
│ └── TestVM.vbox
├── SessionTests.py
├── VirtualBoxTests.py
├── VirtualBoxManagerTests.py
├── VirtualBoxExceptionTests.py
├── StorageControllerTests.py
├── MediumAttachmentTests.py
├── ConstantsTests.py
├── HardDiskTests.py
├── MediumTests.py
├── pyVBoxTest.py
├── DeviceTests.py
└── VirtualMachineTests.py
├── .gitignore
├── pyVBox
├── UUID.py
├── GuestOSType.py
├── HardDisk.py
├── MediumAttachment.py
├── __init__.py
├── StorageController.py
├── Snapshot.py
├── Progress.py
├── VirtualBoxManager.py
├── Wrapper.py
├── Session.py
├── VirtualBox.py
├── VirtualBoxException.py
├── Medium.py
└── VirtualMachine.py
├── setup.py
├── README.markdown
└── utils
└── pyvbox.py
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | test/tmp/*
4 | pyVBox.egg-info/*
5 |
--------------------------------------------------------------------------------
/test/appliances/TestHD.vdi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/von/pyVBox/HEAD/test/appliances/TestHD.vdi
--------------------------------------------------------------------------------
/pyVBox/UUID.py:
--------------------------------------------------------------------------------
1 | """Functions for manipulation UUIDs"""
2 |
3 | import uuid
4 |
5 | def isUUID(s):
6 | """Is the given string a UUID?"""
7 | try:
8 | uuid.UUID(s)
9 | except ValueError:
10 | return False
11 | else:
12 | return True
13 |
--------------------------------------------------------------------------------
/test/SessionTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for Session class"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Session
6 | from pyVBox import VirtualMachine
7 |
8 | class SessionTests(pyVBoxTest):
9 | """Tests for Session class"""
10 |
11 | def testCreate(self):
12 | """Test Session.create() method"""
13 | s = Session.create()
14 | self.assertNotEqual(s, None)
15 |
16 | if __name__ == '__main__':
17 | main()
18 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from setuptools import setup, find_packages
3 |
4 | # Todo: Add dependency for virtualboxapi
5 | # Todo: Sync verion here with version in util/pyvbox.py
6 | setup(
7 | name = "pyVBox",
8 | version = "0.1preview",
9 | packages = find_packages(),
10 | scripts = ['utils/pyvbox.py'],
11 | test_suite = 'test',
12 |
13 | author = "Von Welch",
14 | author_email = "von@vwelch.com",
15 | description = "A shim layer above the VirtualBox Python API",
16 | license = "Apache2",
17 | url = "http://github.com/von/pyVBox"
18 | )
19 |
--------------------------------------------------------------------------------
/test/VirtualBoxTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for Virtualbox"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import VirtualBox
6 | from pyVBox import VirtualBoxException
7 |
8 | class VirtualBoxTests(pyVBoxTest):
9 | """Test case for VirtualBox"""
10 |
11 | def testInit(self):
12 | """Test VirtualBox()"""
13 | vbox = VirtualBox()
14 |
15 | def testGuestOSTypes(self):
16 | """Test VirtualBox.getGustOSTypes()"""
17 | vbox = VirtualBox()
18 | self.assertNotEqual(None, vbox.guestOSTypes)
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
23 |
--------------------------------------------------------------------------------
/pyVBox/GuestOSType.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IGuestOSType"""
2 |
3 | from Wrapper import Wrapper
4 |
5 | class GuestOSType(Wrapper):
6 | # Properties directly inherited from IMachine
7 | _passthruProperties = [
8 | "adapterType",
9 | "description",
10 | "familyDescription",
11 | "familyId",
12 | "id",
13 | "is64Bit",
14 | "recommendedHDD",
15 | "recommendedIOAPIC",
16 | "recommendedRAM",
17 | "recommendedVirtEx",
18 | "recommendedVRAM",
19 | ]
20 |
21 | def __init__(self, iguestOSType):
22 | assert(iguestOSType is not None)
23 | self._wrappedInstance = iguestOSType
24 |
--------------------------------------------------------------------------------
/test/VirtualBoxManagerTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for VirtualboxManager"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import VirtualBoxManager
6 |
7 | class VirtualBoxManagerTests(pyVBoxTest):
8 | """Test case for VirtualBoxManager"""
9 |
10 | def testInit(self):
11 | """Test VirtualBoxManager()"""
12 | vboxManager = VirtualBoxManager()
13 | del vboxManager
14 |
15 | def testGetVirtualBox(self):
16 | """Test VirtualBoxManager.getVirtualBox()"""
17 | vboxManager = VirtualBoxManager()
18 | vbox = vboxManager.getVirtualBox()
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
23 |
--------------------------------------------------------------------------------
/pyVBox/HardDisk.py:
--------------------------------------------------------------------------------
1 | """Presentation of Medium representing HardDisk"""
2 |
3 | from Medium import Device
4 | import VirtualBoxException
5 | from VirtualBoxManager import Constants
6 |
7 | class HardDisk(Device):
8 | type = Constants.DeviceType_HardDisk
9 | _type_str = "hard disk"
10 |
11 | #
12 | # Utility methods
13 | #
14 | @classmethod
15 | def isRegistered(cls, path):
16 | """Is a hard disk at the given path already registered?"""
17 | try:
18 | with VirtualBoxException.ExceptionHandler():
19 | cls.find(path)
20 | except VirtualBoxException.VirtualBoxObjectNotFoundException:
21 | return False
22 | return True
23 |
24 |
--------------------------------------------------------------------------------
/pyVBox/MediumAttachment.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IMediumAttachment"""
2 |
3 | from Medium import Medium, Device
4 | from Wrapper import Wrapper
5 |
6 | class MediumAttachment(Wrapper):
7 | # Properties directly inherited from IMediumAttachment
8 | _passthruProperties = [
9 | "controller",
10 | "port",
11 | "device",
12 | "passthrough",
13 | "bandwidthGroup", # Wrap some day
14 | ]
15 |
16 | _wrappedProperties = [
17 | ( "medium", Medium ),
18 | ( "type", Device.from_type ),
19 | ]
20 |
21 | def __init__(self, mediumAttachment):
22 | """Return a MediumAttachment around the given IMediumAttachment instance"""
23 | assert(mediumAttachment is not None)
24 | self._wrappedInstance = mediumAttachment
25 |
26 |
--------------------------------------------------------------------------------
/pyVBox/__init__.py:
--------------------------------------------------------------------------------
1 | from HardDisk import HardDisk
2 | from Medium import Device
3 | from Medium import DVD
4 | from Medium import Floppy
5 | from Medium import Medium
6 | from Medium import NetworkDevice
7 | from Medium import SharedFolder
8 | from Medium import USBDevice
9 | from Session import Session
10 | from StorageController import StorageController
11 | from VirtualBox import VirtualBox
12 | from VirtualBoxManager import Constants
13 | from VirtualBoxException import ExceptionHandler
14 | from VirtualBoxException import VirtualBoxException
15 | from VirtualBoxException import VirtualBoxFileError
16 | from VirtualBoxException import VirtualBoxFileNotFoundException
17 | from VirtualBoxException import VirtualBoxObjectNotFoundException
18 | from VirtualBoxManager import VirtualBoxManager
19 | from VirtualMachine import VirtualMachine
20 |
--------------------------------------------------------------------------------
/pyVBox/StorageController.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IStorageController"""
2 |
3 | from Wrapper import Wrapper
4 |
5 | class StorageController(Wrapper):
6 | # Properties directly inherited from IStorageController
7 | _passthruProperties = [
8 | "bus",
9 | "controllerType",
10 | "instance",
11 | "maxDevicesPerPortCount",
12 | "maxPortCount",
13 | "minPortCount",
14 | "name",
15 | "portCount",
16 | ]
17 |
18 | def __init__(self, storageController):
19 | """Return a StorageController around the given IStorageController instance"""
20 | assert(storageController is not None)
21 | self._wrappedInstance = storageController
22 |
23 | def __str__(self):
24 | return self.name
25 |
26 | def __unicode__(self):
27 | return self.name
28 |
29 |
--------------------------------------------------------------------------------
/test/VirtualBoxExceptionTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for Virtualbox"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import ExceptionHandler, VirtualBoxException
6 | from pyVBox import VirtualMachine
7 |
8 | class VirtualBoxExceptionTests(pyVBoxTest):
9 | """Test case for VirtualBoxException"""
10 |
11 | def testVirtualBoxException(self):
12 | """Test raise VirtualBoxException()"""
13 | # Not assertRaises is not a context manager until Python 2.7
14 | self.assertRaises(VirtualBoxException, self.raiseVirtualBoxException)
15 |
16 | @staticmethod
17 | def raiseVirtualBoxException():
18 | raise VirtualBoxException("Testing")
19 |
20 | def testExceptionHandler(self):
21 | """Test VirtualBoxException.ExceptionHandler"""
22 | self.assertRaises(VirtualBoxException, self.causeVirtualBoxException)
23 |
24 | @classmethod
25 | def causeVirtualBoxException(cls):
26 | """Should translate VirtualBox exception into a VirtualBoxException"""
27 | with ExceptionHandler():
28 | VirtualMachine.open(cls.bogusVMpath)
29 |
30 | if __name__ == '__main__':
31 | main()
32 |
33 |
--------------------------------------------------------------------------------
/pyVBox/Snapshot.py:
--------------------------------------------------------------------------------
1 | """Wrapper around ISnapshot object"""
2 |
3 | from Wrapper import Wrapper
4 |
5 | class Snapshot(Wrapper):
6 | # Properties directly inherited from ISnapshot
7 | _passthruProperties = [
8 | "children",
9 | "description",
10 | "id",
11 | "machine",
12 | "name",
13 | "online",
14 | "parent",
15 | "timeStamp",
16 | ]
17 |
18 | def __init__(self, isnapshot):
19 | assert(isnapshot is not None)
20 | self._wrappedInstance = isnapshot
21 |
22 | @property
23 | def machine(self):
24 | """Return VirtualMachine object associated wit this snapshot"""
25 | import VirtualMachine
26 | return VirtualMachine(self.machine)
27 |
28 | @property
29 | def parent(self):
30 | """Return parent snapshot (a snapshot this one is based on), or null if the snapshot has no parent (i.e. is the first snapshot). """
31 | if self.parent is None:
32 | return None
33 | return Snapshot(self.parent)
34 |
35 | @property
36 | def children(self):
37 | """Return child snapshots (all snapshots having this one as a parent)."""
38 | return [Snapshot(child) for child in self.children]
39 |
--------------------------------------------------------------------------------
/test/StorageControllerTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for StorageController"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import StorageController
6 | from pyVBox import VirtualMachine
7 |
8 | class StorageControllerTests(pyVBoxTest):
9 | """Test case for StorageController"""
10 |
11 | def testStorageController(self):
12 | """Test StorageController attributes"""
13 | machine = VirtualMachine.open(self.testVMpath)
14 | controllers = machine.getStorageControllers()
15 | # Should be an IDE controller and a SATA controller
16 | self.assertTrue(len(controllers) == 2)
17 | for controller in controllers:
18 | self.assertNotEqual(None, controller.name)
19 | self.assertNotEqual(None, controller.bus)
20 | self.assertNotEqual(None, controller.controllerType)
21 | self.assertNotEqual(None, controller.instance)
22 | self.assertNotEqual(None, controller.maxDevicesPerPortCount)
23 | self.assertNotEqual(None, controller.maxPortCount)
24 | self.assertNotEqual(None, controller.minPortCount)
25 | self.assertNotEqual(None, controller.portCount)
26 | self.assertNotEqual(None, str(controller))
27 |
28 | if __name__ == '__main__':
29 | main()
30 |
--------------------------------------------------------------------------------
/test/MediumAttachmentTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for MediumAttachment"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Constants
6 | from pyVBox import Device
7 | from pyVBox import DVD
8 | from pyVBox import MediumAttachment
9 | from pyVBox import StorageController
10 | from pyVBox import VirtualMachine
11 |
12 | class MediumAttachmentTests(pyVBoxTest):
13 | """Test case for MediumAttachment"""
14 |
15 | def testMediumAttachment(self):
16 | """Test MediumAttachment attributes"""
17 | machine = VirtualMachine.open(self.testVMpath)
18 | attachments = machine.getMediumAttachments()
19 | # Should be one attached DVD drive
20 | self.assertTrue(len(attachments) == 1)
21 | for attachment in attachments:
22 | self.assertNotEqual(None, attachment.controller)
23 | # attachment.medium should be None in this case for a
24 | # empty removable devices
25 | self.assertEqual(None, attachment.medium)
26 | self.assertNotEqual(None, attachment.port)
27 | self.assertNotEqual(None, attachment.device)
28 | self.assertNotEqual(None, attachment.type)
29 | self.assertTrue(isinstance(attachment.type, Device))
30 | self.assertTrue(isinstance(attachment.type, DVD))
31 | self.assertNotEqual(None, attachment.passthrough)
32 | # bandwidthGroup can apparently be None too
33 |
34 | if __name__ == '__main__':
35 | main()
36 |
--------------------------------------------------------------------------------
/test/ConstantsTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for VirtualManager.Constants"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Constants
6 | from pyVBox import VirtualBoxException
7 |
8 | class ConstantsTests(pyVBoxTest):
9 | """Test case for Constants"""
10 |
11 | def testCleanupModeConstants(self):
12 | """Test CleanupMode constants"""
13 | s = Constants.CleanupMode_UnregisterOnly
14 | s = Constants.CleanupMode_DetachAllReturnNone
15 | s = Constants.CleanupMode_DetachAllReturnHardDisksOnly
16 | s = Constants.CleanupMode_Full
17 |
18 | def testDeviceTypeConstants(self):
19 | """Test DeviceType constants"""
20 | s = Constants.DeviceType_HardDisk
21 |
22 | def testMachineStateConstants(self):
23 | """Test MachineState constants"""
24 | s = Constants.MachineState_Aborted
25 | s = Constants.MachineState_PoweredOff
26 | s = Constants.MachineState_Running
27 |
28 | def testSessionStateConstants(self):
29 | """Test SessionState constants"""
30 | s = Constants.SessionState_Null
31 | s = Constants.SessionState_Locked
32 | s = Constants.SessionState_Unlocked
33 | s = Constants.SessionState_Spawning
34 | s = Constants.SessionState_Unlocking
35 |
36 | def testSessionTypeConstants(self):
37 | """Test SessionType constants"""
38 | s = Constants.SessionType_Null
39 | s = Constants.SessionType_WriteLock
40 | s = Constants.SessionType_Remote
41 | s = Constants.SessionType_Shared
42 |
43 |
44 |
45 | if __name__ == '__main__':
46 | main()
47 |
--------------------------------------------------------------------------------
/test/HardDiskTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for HardDisk"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import HardDisk
6 | from pyVBox import VirtualBoxException
7 | from pyVBox import VirtualBoxFileError
8 | from pyVBox import VirtualBoxObjectNotFoundException
9 |
10 | import os.path
11 |
12 | class HardDiskTests(pyVBoxTest):
13 | """Test case for HardDisk"""
14 |
15 | def testOpen(self):
16 | """Test HardDisk.open()"""
17 | harddisk = HardDisk.open(self.testHDpath)
18 | self.assertEqual(os.path.abspath(self.testHDpath), harddisk.location)
19 | self.assertEqual(os.path.basename(self.testHDpath), harddisk.basename())
20 | harddisk.close()
21 |
22 | def testOpenNotFound(self):
23 | """Test HardDisk.open() with not found file"""
24 | self.assertRaises(
25 | VirtualBoxFileError,
26 | HardDisk.open, self.bogusHDpath)
27 |
28 | def testFind(self):
29 | """Test HardDisk.find()"""
30 | self.assertEqual(False, HardDisk.isRegistered(self.testHDpath))
31 | harddisk = HardDisk.open(self.testHDpath)
32 | self.assertEqual(True, HardDisk.isRegistered(self.testHDpath))
33 | hd = HardDisk.find(self.testHDpath)
34 | self.assertEqual(harddisk.id, hd.id)
35 | harddisk.close()
36 | self.assertEqual(False, HardDisk.isRegistered(self.testHDpath))
37 |
38 | def testFindNotFound(self):
39 | """Test HardDisk.find() with not found file"""
40 | self.assertRaises(
41 | VirtualBoxObjectNotFoundException,
42 | HardDisk.find, self.bogusHDpath)
43 |
44 | if __name__ == '__main__':
45 | main()
46 |
--------------------------------------------------------------------------------
/pyVBox/Progress.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IProgress object"""
2 |
3 | import VirtualBoxException
4 | from Wrapper import Wrapper
5 |
6 | class Progress(Wrapper):
7 | # Properties directly inherited from IProgress
8 | _passthruProperties = [
9 | "cancelable",
10 | "canceled",
11 | "completed",
12 | "description",
13 | "errorInfo",
14 | "id",
15 | "initiator",
16 | "operation",
17 | "operationCount",
18 | "operationDescription",
19 | "operationPercent",
20 | "percent",
21 | "resultCode",
22 | "timeout",
23 | "timeRemaining",
24 | ]
25 |
26 | WaitIndefinite = -1
27 |
28 | def __init__(self, progress):
29 | """Return a Progress wrapper around given IProgress instance"""
30 | self._wrappedInstance = progress
31 |
32 | def waitForCompletion(self, timeout = None):
33 | """Waits until the task is done (including all sub-operations).
34 |
35 | Timeout is in milliseconds, specify None for an indefinite wait."""
36 | if timeout is None:
37 | timeout = self.WaitIndefinite
38 | with VirtualBoxException.ExceptionHandler():
39 | self._wrappedInstance.waitForCompletion(timeout)
40 | if (((not self.completed) and (timeout == self.WaitIndefinite)) or
41 | (self.completed and (self.resultCode != 0))):
42 | # TODO: This is not the right exception to return.
43 | raise VirtualBoxException.VirtualBoxException(
44 | "Task %s did not complete: %s (%d)" %
45 | (self.description,
46 | self.errorInfo.text,
47 | self.resultCode))
48 |
49 |
--------------------------------------------------------------------------------
/pyVBox/VirtualBoxManager.py:
--------------------------------------------------------------------------------
1 | """Wrapper around vboxapi.VirtualBoxManager"""
2 |
3 | import vboxapi
4 | import VirtualBoxException
5 |
6 | class VirtualBoxManager(vboxapi.VirtualBoxManager):
7 |
8 | def __init__(self, style=None, params=None):
9 | self.__call_deinit = False
10 | with VirtualBoxException.ExceptionHandler():
11 | vboxapi.VirtualBoxManager.__init__(self, style, params)
12 | self.__call_deinit = True
13 |
14 | def __del__(self):
15 | if self.__call_deinit:
16 | # Not sure what this does. Copying use from vboxshell.py.
17 | vboxapi.VirtualBoxManager.deinit(self)
18 |
19 | def waitForEvents(self, timeout=None):
20 | """Wait for an event.
21 |
22 | Timeout is in miliseconds (I think)."""
23 | if timeout is None:
24 | # No timeout
25 | timeout = 0
26 | with VirtualBoxException.ExceptionHandler():
27 | vboxapi.VirtualBoxManager.waitForEvents(self, timeout)
28 |
29 |
30 | def getIVirtualBox(self):
31 | return self.vbox
32 |
33 | def isMSCOM(self):
34 | """This this a MSCOM manager?"""
35 | return (self.type == 'MSCOM')
36 |
37 | class Constants:
38 | _manager = VirtualBoxManager()
39 |
40 | # Pass any request for unrecognized method or attribute on to
41 | # XPCOM object. We do this since I don't know how to inherit the
42 | # XPCOM class directly.
43 | class __metaclass__(type):
44 | def __getattr__(cls, name):
45 | try:
46 | return getattr(cls._manager.constants, name)
47 | except AttributeError as e:
48 | raise AttributeError("%s.%s not found" % (cls.__name__,
49 | name))
50 |
--------------------------------------------------------------------------------
/test/MediumTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for Medium"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Constants
6 | from pyVBox import HardDisk
7 | from pyVBox import Medium
8 | from pyVBox import VirtualBoxException
9 |
10 |
11 | import os.path
12 |
13 | class MediumTests(pyVBoxTest):
14 | """Test case for Medium"""
15 |
16 | def testMedium(self):
17 | """Test Medium basics"""
18 | harddisk = HardDisk.open(self.testHDpath)
19 | self.assertEqual(os.path.abspath(self.testHDpath), harddisk.location)
20 | self.assertEqual(os.path.basename(self.testHDpath), harddisk.basename())
21 | self.assertEqual(os.path.dirname(os.path.abspath(self.testHDpath)),
22 | harddisk.dirname())
23 | self.assertEqual(os.path.basename(self.testHDpath), str(harddisk))
24 | self.assertNotEqual(None, harddisk.id)
25 | self.assertNotEqual(None, harddisk.getIMedium())
26 | self.assertNotEqual(None, harddisk.name)
27 | harddisk.close()
28 |
29 | def testGetType(self):
30 | """Test Medium.deviceType"""
31 | harddisk = HardDisk.open(self.testHDpath)
32 | self.assertEqual(harddisk.deviceType, HardDisk)
33 |
34 | def testCreate(self):
35 | """Test Medium.create()"""
36 | medium = Medium.create(self.cloneHDpath)
37 | self.assertEqual(Constants.MediumState_NotCreated, medium.state)
38 |
39 | def testCreateWithStorage(self):
40 | """Test Medium.createWithStorage()"""
41 | medium = Medium.createWithStorage(self.cloneHDpath, 1)
42 | self.assertEqual(Constants.MediumState_Created, medium.state)
43 |
44 | def testClone(self):
45 | """Test Medium.clone()"""
46 | harddisk = HardDisk.open(self.testHDpath)
47 | harddisk.clone(self.cloneHDpath)
48 | clonedisk = HardDisk.find(self.cloneHDpath)
49 | self.assertEqual(harddisk.format, harddisk.format)
50 | self.assertEqual(harddisk.logicalSize, harddisk.logicalSize)
51 | self.assertNotEqual(harddisk.id, clonedisk.id)
52 |
53 | if __name__ == '__main__':
54 | main()
55 |
--------------------------------------------------------------------------------
/pyVBox/Wrapper.py:
--------------------------------------------------------------------------------
1 | import VirtualBoxException
2 |
3 | class Wrapper(object):
4 | """Base class for wrappers around VirtualBox XPCOM-based objects.
5 |
6 | This class is not usable by itself, it must be inherited by a child
7 | class and certain properties set:
8 |
9 | _wrappedInstance should be the wrapped instance.
10 |
11 | _passthruProperties is the directly exposed properties (as
12 | strings) of the wrapped class. These properties can be retrieved or
13 | set, but not deleted. In general, these should be properties that are
14 | native Python types, other properties should be wrapped.
15 |
16 | _wrappedProperties is a list of tuples. When the attribute named
17 | by the first element is accessed, function or class in the second
18 | element is invoked with the property as an argument and the result is
19 | returned.
20 |
21 | Utilizing this class since I don't kow how to inherit the XPCOM
22 | classes directly.
23 | """
24 | _wrappedInstance = None
25 | _passthruProperties = []
26 | _wrappedProperties = []
27 |
28 | def __getattr__(self, attr):
29 | if self._wrappedInstance:
30 | if attr in self._passthruProperties:
31 | with VirtualBoxException.ExceptionHandler():
32 | return getattr(self._wrappedInstance, attr)
33 | for prop, func in self._wrappedProperties:
34 | if prop == attr:
35 | with VirtualBoxException.ExceptionHandler():
36 | value = getattr(self._wrappedInstance, attr)
37 | return func(value) if value else None
38 | raise AttributeError("Unrecognized attribute '%s'" % attr)
39 |
40 | def __setattr__(self, attr, value):
41 | if self._wrappedInstance and (attr in self._passthruProperties):
42 | with VirtualBoxException.ExceptionHandler():
43 | setattr(self._wrappedInstance, attr, value)
44 | self.__dict__[attr] = value
45 |
46 | def __delattr__(self, attr):
47 | if self._wrappedInstance and (attr in self._passthruProperties):
48 | with VirtualBoxException.ExceptionHandler():
49 | raise AttributeError("Cannot delete attribute '%s'" % attr)
50 | del self.__dict__[attr]
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/pyVBoxTest.py:
--------------------------------------------------------------------------------
1 | """Base class for all pyVBox unittests."""
2 |
3 | import os
4 | import os.path
5 | import shutil
6 | import unittest
7 |
8 | from pyVBox import HardDisk
9 | from pyVBox import VirtualBox
10 | from pyVBox import VirtualMachine
11 |
12 | class pyVBoxTest(unittest.TestCase):
13 | """Base class for all pyVBox unittests."""
14 | # VM and HD for testing
15 | # These are version controlled, we will make a copy before
16 | # altering them.
17 | testHDsrc = "test/appliances/TestHD.vdi"
18 | testHDUUID = "c92b558e-eba5-43e8-a8b3-984f946db1b2"
19 | testVMsrc = "test/appliances/TestVM.vbox"
20 | testVMname = "TestVM"
21 |
22 | # Our testing grounds
23 | testPath = "test/tmp/"
24 |
25 | # setUp() will create these copies of the sources above
26 | testHDpath = "test/tmp/TestHD.vdi"
27 | testVMpath = "test/tmp/TestVM.xml"
28 |
29 | # Clone we will create during testing
30 | cloneVMname = "CloneTestVM"
31 | cloneHDpath = "test/tmp/CloneHD.vdi"
32 |
33 | # Paths for testing failure
34 | bogusHDpath = "/bogus/path"
35 | bogusVMpath = "/bogus/path"
36 |
37 | def setUp(self):
38 | if not os.path.exists(self.testPath):
39 | os.mkdir(self.testPath)
40 | shutil.copy(self.testVMsrc, self.testVMpath)
41 | shutil.copy(self.testHDsrc, self.testHDpath)
42 | self._cleanup()
43 |
44 | def tearDown(self):
45 | self._cleanup()
46 |
47 | def _cleanup(self):
48 | """Unregister test HD and VM if they are registered."""
49 | # Do machine first to detach any HDs
50 | try:
51 | machine = VirtualMachine.find(self.testVMname)
52 | except:
53 | pass
54 | else:
55 | machine.eject()
56 | try:
57 | harddisk = HardDisk.find(self.testHDpath)
58 | except:
59 | pass
60 | else:
61 | harddisk.close()
62 | try:
63 | machine = VirtualMachine.find(self.cloneVMname)
64 | except Exception as e:
65 | pass
66 | else:
67 | machine.eject()
68 | machine.delete()
69 | try:
70 | clonedisk = HardDisk.find(self.cloneHDpath)
71 | except:
72 | pass
73 | else:
74 | clonedisk.close()
75 | try:
76 | os.remove(self.cloneHDpath)
77 | except:
78 | pass
79 |
80 | def main():
81 | """Run tests."""
82 | unittest.main()
83 |
--------------------------------------------------------------------------------
/pyVBox/Session.py:
--------------------------------------------------------------------------------
1 | """Wrapper around ISession object"""
2 |
3 | from Progress import Progress
4 | from VirtualBox import VirtualBox
5 | import VirtualBoxException
6 | from VirtualBoxManager import Constants, VirtualBoxManager
7 | from Wrapper import Wrapper
8 |
9 | import weakref
10 |
11 | STATE_NAME = {
12 | Constants.SessionState_Null : "Null",
13 | Constants.SessionState_Locked : "Locked",
14 | Constants.SessionState_Unlocked : "Unlocked",
15 | Constants.SessionState_Spawning : "Spawning",
16 | Constants.SessionState_Unlocking : "Unlocking",
17 | }
18 |
19 | class Session(Wrapper):
20 | # Properties directly inherited from IMachine
21 | _passthruProperties = [
22 | "console",
23 | "state",
24 | "type",
25 | ]
26 |
27 | _manager = VirtualBoxManager()
28 | _vbox = VirtualBox()
29 |
30 | def __init__(self, isession):
31 | self._wrappedInstance = isession
32 | self._machine = None
33 |
34 | @classmethod
35 | def create(cls):
36 | """Return a new Session instance"""
37 | return cls(cls._createSession())
38 |
39 | @classmethod
40 | def _createSession(cls):
41 | """Create and return an ISesison object."""
42 | return cls._manager.mgr.getSessionObject(cls._vbox)
43 |
44 | def __del__(self):
45 | self.unlockMachine(wait=False)
46 |
47 | def saveSettings(self):
48 | """Save changes to VM associated with session."""
49 | with VirtualBoxException.ExceptionHandler():
50 | self.getIMachine().saveSettings()
51 |
52 | def _setMachine(self, machine):
53 | """Set the machine associated with this session."""
54 | self._machine = machine
55 |
56 | def getMachine(self):
57 | """Return the mutable machine associated with the session."""
58 | return self._machine
59 |
60 | def unlockMachine(self, wait=True):
61 | """Close any open session, unlocking the machine."""
62 | if self.isLocked():
63 | with VirtualBoxException.ExceptionHandler():
64 | self._wrappedInstance.unlockMachine()
65 | if wait:
66 | while self.isLocked():
67 | self._vbox.waitForEvent()
68 |
69 | def getISession(self):
70 | """Return ISession instance wrapped by Session"""
71 | return self._wrappedInstance
72 |
73 | def getIMachine(self):
74 | """Return mutable IMachine object associated with session."""
75 | return self._wrappedInstance.machine
76 |
77 | def isDirect(self):
78 | """Is this a direct session?"""
79 | return (self.type != Constants.SessionType_Remote)
80 |
81 | def isLocked(self):
82 | """Is this session locked?"""
83 | return (self.state == Constants.SessionState_Locked)
84 |
85 | def isUnlocked(self):
86 | """Is this session unlocked?"""
87 | return (self.state == Constants.SessionState_Unlocked)
88 |
89 |
--------------------------------------------------------------------------------
/pyVBox/VirtualBox.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IVirtualBox
2 |
3 | This is not used at this time."""
4 |
5 | from GuestOSType import GuestOSType
6 | from VirtualBoxException import VirtualBoxException
7 | from VirtualBoxManager import VirtualBoxManager
8 | from Wrapper import Wrapper
9 |
10 | import os.path
11 |
12 | class VirtualBox(Wrapper):
13 | # Properties directly inherited from IVirtualMachine
14 | _passthruProperties = [
15 | "homeFolder",
16 | "packageType",
17 | "revision",
18 | "settingsFilePath",
19 | "version",
20 |
21 | # Also allow direct access to these methods. These shouldn't
22 | # be used directly, buy only by other pyVBox classes.
23 | "createMachine",
24 | "findMachine",
25 | "getMachine",
26 | "openExistingSession",
27 | "openMachine",
28 | "openRemoteSession",
29 | "openSession",
30 | "registerMachine",
31 | ]
32 |
33 | def __init__(self):
34 | self._manager = VirtualBoxManager()
35 | self._wrappedInstance = self._manager.getIVirtualBox()
36 |
37 | def getGuestOSType(self, osTypeId):
38 | """Returns an object describing the specified guest OS type."""
39 | iosType = self._wrappedInstance.getGuestOSType(osTypeId)
40 | return GuestOSType(iosType)
41 |
42 | @property
43 | def guestOSTypes(self):
44 | """Return an array of all available guest OS Types."""
45 | return [GuestOSType(t) for t in self._getArray('guestOSTypes')]
46 |
47 | @property
48 | def machines(self):
49 | """Return array of machine objects registered within this VirtualBox instance."""
50 | from VirtualMachine import VirtualMachine
51 | return [VirtualMachine(vm) for vm in self._getArray('machines')]
52 |
53 | def waitForEvent(self):
54 | self._manager.waitForEvents()
55 |
56 | def _getArray(self, arrayName):
57 | """Return the array identified by the given name"""
58 | return self._manager.getArray(self._wrappedInstance, arrayName)
59 |
60 |
61 | class VirtualBoxMonitor:
62 | def __init__(self, vbox):
63 | self._vbox = vbox
64 | self._manager = VirtualBoxManager()
65 | self._isMscom = self._manager.isMSCOM()
66 |
67 | def onMachineStateChange(self, id, state):
68 | pass
69 |
70 | def onMachineDataChange(self, id):
71 | pass
72 |
73 | def onExtraDataCanChange(self, id, key, value):
74 | # Witty COM bridge thinks if someone wishes to return tuple, hresult
75 | # is one of values we want to return
76 | if self._isMscom:
77 | return "", 0, True
78 | else:
79 | return True, ""
80 |
81 | def onExtraDataChange(self, id, key, value):
82 | pass
83 |
84 | def onMediaRegistered(self, id, type, registered):
85 | pass
86 |
87 | def onMachineRegistered(self, id, registred):
88 | pass
89 |
90 | def onSessionStateChange(self, id, state):
91 | pass
92 |
93 | def onSnapshotTaken(self, mach, id):
94 | pass
95 |
96 | def onSnapshotDiscarded(self, mach, id):
97 | pass
98 |
99 | def onSnapshotChange(self, mach, id):
100 | pass
101 |
102 | def onGuestPropertyChange(self, id, name, newValue, flags):
103 | pass
104 |
--------------------------------------------------------------------------------
/test/DeviceTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for Device"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Constants
6 |
7 | class DeviceTests(pyVBoxTest):
8 | """Test cases for Device"""
9 |
10 | def testDevice(self):
11 | """Test Device class"""
12 | from pyVBox import Device
13 | self.assertEqual(Device.type, None)
14 |
15 | def testFromType(self):
16 | """Test Device.from_type()"""
17 | from pyVBox import Device, DVD, Floppy, \
18 | HardDisk, NetworkDevice, USBDevice, SharedFolder
19 | self.assertTrue(
20 | isinstance(Device.from_type(Constants.DeviceType_Floppy),
21 | Floppy))
22 | self.assertTrue(
23 | isinstance(Device.from_type(Constants.DeviceType_DVD),
24 | DVD))
25 | self.assertTrue(
26 | isinstance(Device.from_type(Constants.DeviceType_Network),
27 | NetworkDevice))
28 | self.assertTrue(
29 | isinstance(Device.from_type(Constants.DeviceType_USB),
30 | USBDevice))
31 | self.assertTrue(
32 | isinstance(Device.from_type(Constants.DeviceType_SharedFolder),
33 | SharedFolder))
34 |
35 | def testClassFromType(self):
36 | """Test Device.class_from_type()"""
37 | from pyVBox import Device, DVD, Floppy, \
38 | HardDisk, NetworkDevice, USBDevice, SharedFolder
39 | self.assertEqual(
40 | Device.class_from_type(Constants.DeviceType_Floppy),
41 | Floppy)
42 | self.assertEqual(
43 | Device.class_from_type(Constants.DeviceType_DVD),
44 | DVD)
45 | self.assertEqual(
46 | Device.class_from_type(Constants.DeviceType_Network),
47 | NetworkDevice)
48 | self.assertEqual(
49 | Device.class_from_type(Constants.DeviceType_USB),
50 | USBDevice)
51 | self.assertEqual(
52 | Device.class_from_type(Constants.DeviceType_SharedFolder),
53 | SharedFolder)
54 |
55 | def testFloppy(self):
56 | """Test Floppy"""
57 | from pyVBox import Device, Floppy
58 | floppy = Device.from_type(Constants.DeviceType_Floppy)
59 | self.assertTrue(isinstance(floppy, Floppy))
60 | self.assertEqual(str(floppy), "floppy")
61 |
62 | def testDVD(self):
63 | """Test DVD"""
64 | from pyVBox import Device, DVD
65 | dvd = Device.from_type(Constants.DeviceType_DVD)
66 | self.assertTrue(isinstance(dvd, DVD))
67 | self.assertEqual(str(dvd), "DVD")
68 |
69 | def testNetworkDevice(self):
70 | """Test NetworkDevice"""
71 | from pyVBox import Device, NetworkDevice
72 | nd = Device.from_type(Constants.DeviceType_Network)
73 | self.assertTrue(isinstance(nd, NetworkDevice))
74 | self.assertEqual(str(nd), "network device")
75 |
76 | def testUSBDevice(self):
77 | """Test USBDevice"""
78 | from pyVBox import Device, USBDevice
79 | usb = Device.from_type(Constants.DeviceType_USB)
80 | self.assertTrue(isinstance(usb, USBDevice))
81 | self.assertEqual(str(usb), "USB device")
82 |
83 | def testNetworkDevice(self):
84 | """Test SharedFolder"""
85 | from pyVBox import Device, SharedFolder
86 | folder = Device.from_type(Constants.DeviceType_SharedFolder)
87 | self.assertTrue(isinstance(folder, SharedFolder))
88 | self.assertEqual(str(folder), "shared folder")
89 |
90 | if __name__ == '__main__':
91 | main()
92 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | This is pyVBOX, a shim layer above the VirtualBox Python API.
4 |
5 | Why? Becuase the VirtualBox Python API is somewhat complex and I got
6 | tried of trying to remember all the details of its use. Plus it
7 | changes from release to release and this gives me an abstraction layer
8 | to hide those changes. The software also includes a script,
9 | utils/pyVbox.py, that provides the ability to manipulate VMs (like
10 | VBoxMange).
11 |
12 | This code is written to the 4.1 version of VirtualBox (4.1.12
13 | specifically). I have not tried it against any other version.
14 |
15 | This software is independently created from VirtualBox and Oracle. No
16 | endorsement by Oracle or the VirtualBox authors is implied.
17 |
18 | Use at your own risk. No support guarenteed, but I'm happy to receive
19 | bug reports or suggestions (open an issue please).
20 |
21 | This code is released under the [Apache License, Version
22 | 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). I ask you send
23 | me email (von@vwelch.com) if you add any enhancements or just find it
24 | useful (open an issue to report a bug please).
25 |
26 | Kudos to [Nikolay Igotti](http://blogs.sun.com/nike/entry/python_api_to_the_virtualbox)
27 | and the vboxshell.py example that comes with the Virtualbox SDK.
28 |
29 | The home page for this code is [http://github.com/von/pyVBox](http://github.com/von/pyVBox).
30 |
31 | # Goals
32 |
33 | My goal is to provide functionality in the pyVBox library, exposed
34 | through the pyVBox.py script, to do the following:
35 |
36 | * Given a VM on a USB drive, register it, boot it and then when it
37 | completes unregister it, all with one command. *DONE:* the 'boot'
38 | command provides this.
39 |
40 | * Allow me to backup a running VM. *DONE:* the 'backup' command will
41 | suspend a VM, if needed, and make backup copies of all its attached
42 | hard drives. I'd still like to play around with using snapshops so
43 | that the VM doesn't have to be suspended.
44 |
45 | * Allow me to make a copy of a VM with one command. I make what I call
46 | 'base VMs' for different OS'es and then when I want a VM for a
47 | specific application I copy the base VM and customize the copy. You
48 | can do this with a combination of VBoxManage and the VirutalBox GUI,
49 | but it's a multi-step process. *DONE:* the 'clone' command will clone
50 | a VM, not perfectly, but well enough the clone will boot.
51 |
52 | One I accomplish the above, I'll probably release 1.0 and then decide
53 | what comes next.
54 |
55 | # Installing vboxapi
56 |
57 | pyVBox relies on the vboxapi that comes with the VirtualBox SDK from
58 | Oracle. You must install it first before trying to use pyVBox.
59 |
60 | 1. Download and install VirtualBox from [the VirtualBox downloads page](http://www.virtualbox.org/wiki/Downloads).
61 |
62 | 1. Download the VirtualBox SDK from [the VirtualBox downloads page](http://www.virtualbox.org/wiki/Downloads).
63 |
64 | 1. Unzip the SDK somewhere
65 |
66 | 1. cd sdk/installer
67 |
68 | 1. Set your VBOX_INSTALL_PATH via:
69 |
70 | # VBOXMANAGE_PATH=`which VBoxManage`
71 | # export VBOX_INSTALL_PATH=`basename $VBOXMANAGE_PATH`
72 |
73 | 1. Install:
74 |
75 | # python vboxapisetup.py install
76 |
77 | # Setting up your environment
78 |
79 | (TODO: Update this to use setup.py)
80 |
81 | You will need to include two paths in your PYTHONPATH environent variable:
82 |
83 | 1. The directory containing the pyVBox module directory. I.e. the
84 | directory in which you found this README.
85 |
86 | 1. The bindings/xpcom/python/ directory in the VirtualBox SDK distribution.
87 |
88 | For example (assuming your current directory is the one where you
89 | found this README file, and you unpacked the VirtualBox SDK to
90 | /usr/local/virtualbox-sdk):
91 |
92 | setenv PYTHONPATH `pwd`:/usr/local/virtualbox-sdk/bindings/xpcom/python/
93 |
94 | # Philosophy
95 |
96 | Basically I'm creating wrappers around the VirtualBox Python API that adhere to the OO interface it provides, with a few tweaks:
97 |
98 | * Provide reasonable defaults for the VirtualBox methods where it
99 | makes sense. Where 'reasonable' of course means what I consider to be
100 | reasonable.
101 |
102 | * Provider for some higher-level functionality. E.g. for a
103 | VirtualMachine I provide a eject() method that does whatever is needed
104 | to unregister the VM.
105 |
106 | * In order to make things more intuitive, I move methods to the class
107 | they are associated with. For example, to find a VM you use
108 | VirtualMachine.find() instead of VirtualBox.findMachine(). Again
109 | 'intuitive' is what I consider to be intuitive.
110 |
111 | For all the classes you should still be able to access any of the
112 | provider VirtualBox methods, attributes, etc. (I.e. I'm subclassing
113 | them, or the equivalent there of).
114 |
115 | # Tests
116 |
117 | There is a test suite in test/ which you can invoke with:
118 |
119 | python setup.py test
120 |
121 | # Other issues
122 |
123 | ## Python Version on the Mac
124 |
125 | On Mac, you need to use python that came with OS or you will get a 'Cannot
126 | find VBoxPython module' error. See [this thread](http://forums.virtualbox.org/viewtopic.php?f=8&t=18969).
127 |
--------------------------------------------------------------------------------
/test/appliances/TestVM.vbox:
--------------------------------------------------------------------------------
1 |
2 |
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 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/pyVBox/VirtualBoxException.py:
--------------------------------------------------------------------------------
1 | """Basic exceptions for pyVBox."""
2 |
3 | import xpcom
4 |
5 | from contextlib import contextmanager
6 | import sys
7 |
8 | ######################################################################
9 | # Constants from IDL file. I can't find a way to programmatically
10 | # get at these through the python API.
11 |
12 | # Object corresponding to the supplied arguments does not exist.
13 | VBOX_E_OBJECT_NOT_FOUND = 0x80BB0001
14 |
15 | # Current virtual machine state prevents the operation.
16 | VBOX_E_INVALID_VM_STATE = 0x80BB0002
17 |
18 | # Virtual machine error occurred attempting the operation.
19 | VBOX_E_VM_ERROR = 0x80BB0003
20 |
21 | # File not accessible or erroneous file contents.
22 | VBOX_E_FILE_ERROR = 0x80BB0004
23 |
24 | # Runtime subsystem error.
25 | VBOX_E_IPRT_ERROR = 0x80BB0005
26 |
27 | # Pluggable Device Manager error.
28 | VBOX_E_PDM_ERROR = 0x80BB0006
29 |
30 | # Current object state prohibits operation.
31 | VBOX_E_INVALID_OBJECT_STATE = 0x80BB0007
32 |
33 | # Host operating system related error.
34 | VBOX_E_HOST_ERROR = 0x80BB0008
35 |
36 | # Requested operation is not supported.
37 | VBOX_E_NOT_SUPPORTED = 0x80BB0009
38 |
39 | # Invalid XML found.
40 | VBOX_E_XML_ERROR = 0x80BB000A
41 |
42 | # Current session state prohibits operation.
43 | VBOX_E_INVALID_SESSION_STATE = 0x80BB000B
44 |
45 | # Object being in use prohibits operation.
46 | VBOX_E_OBJECT_IN_USE = 0x80BB000C
47 |
48 | ######################################################################
49 | # Constants I've found experimentally. Names are of my own creation.
50 |
51 | # Returned from Progress.waitForCompletion()
52 | VBOX_E_ERROR_ABORT = 0x80004004
53 |
54 | # Returned when VirtualMachine.open() method doesn't find a file
55 | VBOX_E_FILE_NOT_FOUND = 0x80004005
56 |
57 | # Thrown if a create VirtualMachine is invalid in some way
58 | XPCOM_E_OBJECT_NOT_READY = 0x80070005
59 |
60 | # Returned if VirtualMachine.memorySize is set out of range
61 | XPCOM_E_INVALID_ARGUMENT = 0x80070057
62 |
63 | # "Call to remote object failed"
64 | NS_ERROR_CALL_FAILED = 0x800706be
65 |
66 | # Returned when getting machine attribute from closed session
67 | VBOX_E_SESSION_CLOSED = 0x8000ffff
68 |
69 | ######################################################################
70 |
71 | class VirtualBoxException(Exception):
72 | """Base exception for pyVBox exceptions."""
73 | pass
74 |
75 | class VirtualBoxObjectNotFoundException(VirtualBoxException):
76 | """Object corresponding to the supplied arguments does not exist."""
77 | errno = VBOX_E_OBJECT_NOT_FOUND
78 |
79 | class VirtualBoxInvalidVMStateException(VirtualBoxException):
80 | """Current virtual machine state prevents the operation."""
81 | errno = VBOX_E_INVALID_VM_STATE
82 |
83 | class VirtualBoxVMError(VirtualBoxException):
84 | """Virtual machine error occurred attempting the operation."""
85 | errno = VBOX_E_VM_ERROR
86 |
87 | class VirtualBoxFileError(VirtualBoxException):
88 | """File not accessible or erroneous file contents."""
89 | errno = VBOX_E_FILE_ERROR
90 |
91 | class VirtualBoxRuntimeSubsystemError(VirtualBoxException):
92 | """Runtime subsystem error."""
93 | errno = VBOX_E_IPRT_ERROR
94 |
95 | class VirtualBoxPluggableDeviceManagerError(VirtualBoxException):
96 | """Pluggable Device Manager error."""
97 | errno = VBOX_E_PDM_ERROR
98 |
99 | class VirtualBoxInvalidObjectState(VirtualBoxException):
100 | """Current object state prohibits operation."""
101 | errno = VBOX_E_INVALID_OBJECT_STATE
102 |
103 | class VirtualBoxHostError(VirtualBoxException):
104 | """Host operating system related error."""
105 | errno = VBOX_E_HOST_ERROR
106 |
107 | class VirtualBoxNotSupportException(VirtualBoxException):
108 | """Requested operation is not supported."""
109 | errno = VBOX_E_NOT_SUPPORTED
110 |
111 | class VirtualBoxInvalidXMLError(VirtualBoxException):
112 | """Invalid XML found."""
113 | errno = VBOX_E_XML_ERROR
114 |
115 | class VirtualBoxInvalidSessionStateException(VirtualBoxException):
116 | """Current session state prohibits operation."""
117 | errno = VBOX_E_INVALID_SESSION_STATE
118 |
119 | class VirtualBoxObjectInUseException(VirtualBoxException):
120 | """Object being in use prohibits operation."""
121 | errno = VBOX_E_OBJECT_IN_USE
122 |
123 | class VirtualBoxFileNotFoundException(VirtualBoxException):
124 | """File not found."""
125 | errno = VBOX_E_FILE_NOT_FOUND
126 |
127 | class VirtualBoxObjectNotReady(VirtualBoxException):
128 | """Object not read."""
129 | errno = XPCOM_E_OBJECT_NOT_READY
130 |
131 | class VirtualBoxInvalidArgument(VirtualBoxException):
132 | """Invalid argument."""
133 | errno = XPCOM_E_INVALID_ARGUMENT
134 |
135 | class VirtualBoxOperationAborted(VirtualBoxException):
136 | """Operation aborted."""
137 | errno = VBOX_E_ERROR_ABORT
138 |
139 | class VirtualBoxCallFailed(VirtualBoxException):
140 | """Call to remot object failed."""
141 | errno = NS_ERROR_CALL_FAILED
142 |
143 | # Mappings from VirtualBox error numbers to pyVBox classes
144 | EXCEPTION_MAPPINGS = {
145 | VBOX_E_OBJECT_NOT_FOUND : VirtualBoxObjectNotFoundException,
146 | VBOX_E_INVALID_VM_STATE : VirtualBoxInvalidVMStateException,
147 | VBOX_E_VM_ERROR : VirtualBoxVMError,
148 | VBOX_E_FILE_ERROR : VirtualBoxFileError,
149 | VBOX_E_IPRT_ERROR : VirtualBoxRuntimeSubsystemError,
150 | VBOX_E_PDM_ERROR : VirtualBoxPluggableDeviceManagerError,
151 | VBOX_E_INVALID_OBJECT_STATE : VirtualBoxInvalidObjectState,
152 | VBOX_E_HOST_ERROR : VirtualBoxHostError,
153 | VBOX_E_NOT_SUPPORTED : VirtualBoxNotSupportException,
154 | VBOX_E_XML_ERROR : VirtualBoxInvalidXMLError,
155 | VBOX_E_INVALID_SESSION_STATE : VirtualBoxInvalidSessionStateException,
156 | VBOX_E_OBJECT_IN_USE : VirtualBoxObjectInUseException,
157 | VBOX_E_ERROR_ABORT : VirtualBoxOperationAborted,
158 | VBOX_E_FILE_NOT_FOUND : VirtualBoxFileNotFoundException,
159 | XPCOM_E_OBJECT_NOT_READY : VirtualBoxObjectNotReady,
160 | XPCOM_E_INVALID_ARGUMENT : VirtualBoxInvalidArgument,
161 | VBOX_E_SESSION_CLOSED : VirtualBoxInvalidVMStateException,
162 | NS_ERROR_CALL_FAILED : VirtualBoxCallFailed,
163 | }
164 |
165 |
166 | class ExceptionHandler:
167 | """Context manager to handle any VirtualBox exceptions.
168 |
169 | Since the VirtualBox Python API raises normal exceptions, this
170 | function determines if an exception is related to VirtualBox and, if
171 | so, raises its pyVBox equivalent.
172 |
173 | Otherwise, it does nothing.
174 |
175 | The function should be used as follows:
176 |
177 | with vbox_exception_handler():
178 | # Some VirtualBox code here
179 | """
180 |
181 | def __enter__(self):
182 | return self
183 |
184 | def __exit__(self, exc_type, exc_val, exc_tb):
185 | if exc_type is not None:
186 | if issubclass(exc_type, xpcom.Exception): # Also True if equal
187 | errno, message = exc_val
188 | exception_class = None
189 | if EXCEPTION_MAPPINGS.has_key(errno):
190 | exception_class = EXCEPTION_MAPPINGS[errno]
191 | else:
192 | # Convert errno from exception to constant value from
193 | # IDL file. I don't understand why this is needed,
194 | # determined experimentally. ex.errno is a negative
195 | # value (e.g. -0x7f44ffff), this effectively takes its
196 | # aboslute value and subtracts it from 0x100000000.
197 | errno = 0x100000000 + errno
198 | if EXCEPTION_MAPPINGS.has_key(errno):
199 | exception_class = EXCEPTION_MAPPINGS[errno]
200 | if exception_class:
201 | # Reraise with original stacktrace and instance
202 | # information, but with new class. Note that one
203 | # cannot hide the current line from the traceback. See
204 | # http://stackoverflow.com/questions/6410764/raising-exceptions-without-raise-in-the-traceback
205 | raise exception_class, message
206 | # Else we don't have or don't recognize the errno, return
207 | # and allow context manager to re-raise exception.
208 | return False
209 |
210 |
--------------------------------------------------------------------------------
/pyVBox/Medium.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IMedium object"""
2 |
3 | from Progress import Progress
4 | import UUID
5 | import VirtualBoxException
6 | from VirtualBoxManager import Constants, VirtualBoxManager
7 | from Wrapper import Wrapper
8 |
9 | import os.path
10 |
11 | class Device(object):
12 | """Abstract class wrapping DeviceType
13 |
14 | Provides convienence functions for associcated Mediums.
15 |
16 | type is DeviceType constant (e.g. Constants.DeviceType_DVD).
17 | """
18 | type = None
19 | _type_str = "undefined device"
20 |
21 | @classmethod
22 | def class_from_type(cls, type):
23 | """Given a type, return appropriate class"""
24 | for cls in cls.__subclasses__():
25 | if type == cls.type:
26 | return cls
27 | raise ValueError("Unknown Device type \"%d\"" % type)
28 |
29 | @classmethod
30 | def from_type(cls, type):
31 | """Given a type, return appropriate instance"""
32 | cls = cls.class_from_type(type)
33 | return cls()
34 |
35 | #
36 | # Medium creation methods
37 | #
38 | @classmethod
39 | def open(cls, path, accessMode = None):
40 | """Open an instance of medium for the given device."""
41 | return Medium.open(path, cls.type, accessMode)
42 |
43 | @classmethod
44 | def find(cls, path):
45 | """Find a medium for type using given path or UUID."""
46 | return Medium.find(path, cls.type)
47 |
48 | def __str__(self):
49 | return self._type_str
50 |
51 | def __unicode__(self):
52 | return self._type_str
53 |
54 | class Floppy(Device):
55 | type = Constants.DeviceType_Floppy
56 | _type_str = "floppy"
57 |
58 | class DVD(Device):
59 | type = Constants.DeviceType_DVD
60 | _type_str = "DVD"
61 |
62 | class NetworkDevice(Device):
63 | type = Constants.DeviceType_Network
64 | _type_str = "network device"
65 |
66 | class USBDevice(Device):
67 | type = Constants.DeviceType_USB
68 | _type_str = "USB device"
69 |
70 | class SharedFolder(Device):
71 | type = Constants.DeviceType_SharedFolder
72 | _type_str = "shared folder"
73 |
74 |
75 | class Medium(Wrapper):
76 | # Properties directly inherited from IMedium
77 | _passthruProperties = [
78 | "autoResize",
79 | "description",
80 | "format",
81 | "hostDrive",
82 | "id",
83 | "lastAccessError",
84 | "location",
85 | "logicalSize",
86 | "name",
87 | "readOnly",
88 | "size",
89 | "state",
90 | "type"
91 | ]
92 |
93 | # These properties are converted by given function before being returned.
94 | _wrappedProperties = [
95 | ("deviceType", Device.class_from_type),
96 | ]
97 |
98 | _manager = VirtualBoxManager()
99 |
100 | def __init__(self, imedium):
101 | """Return a Medium wrapper around given IMedium instance"""
102 | assert(imedium is not None)
103 | self._wrappedInstance = imedium
104 |
105 | #
106 | # Creation methods
107 | #
108 | @classmethod
109 | def open(cls, path, deviceType, accessMode = None, forceNewUuid=False):
110 | """Opens a medium from an existing location.
111 |
112 | Throws VirtualBoxFileError if file not found."""
113 | with VirtualBoxException.ExceptionHandler():
114 | if accessMode is None:
115 | accessMode = Constants.AccessMode_ReadWrite
116 | # path must be absolute path
117 | path = cls._canonicalizeMediumPath(path)
118 | medium = cls._getVBox().openMedium(path,
119 | deviceType,
120 | accessMode,
121 | forceNewUuid)
122 | return Medium(medium)
123 |
124 | @classmethod
125 | def find(cls, path, deviceType):
126 | """Returns a medium that uses the given path or UUID to store medium data."""
127 | with VirtualBoxException.ExceptionHandler():
128 | if not UUID.isUUID(path):
129 | path = cls._canonicalizeMediumPath(path)
130 | medium = cls._getVBox().findMedium(path, deviceType)
131 | return Medium(medium)
132 |
133 | def clone(self, path, newUUID=True, wait=True):
134 | """Create a clone of this medium at the given location.
135 |
136 | If wait is True, does not return until process completes.
137 | if newUUID is true, clone will have new UUID and will be registered, otherwise will have same UUID as source medium.
138 | Returns Progress instance."""
139 | with VirtualBoxException.ExceptionHandler():
140 | path = self._canonicalizeMediumPath(path)
141 | if newUUID:
142 | # If target doesn't have storage, new UUID is created.
143 | target= self.create(path)
144 | else:
145 | # If target does have storage, UUID is copied.
146 | target = self.createWithStorage(path, self.logicalSize)
147 | progress = self.cloneTo(target, wait=wait)
148 | if wait:
149 | progress.waitForCompletion()
150 | return progress
151 |
152 | @classmethod
153 | def create(cls, path, format=None):
154 | """Create a new hard disk at the given location."""
155 | with VirtualBoxException.ExceptionHandler():
156 | path = cls._canonicalizeMediumPath(path)
157 | if os.path.exists(path):
158 | # Todo: Better exception here
159 | raise VirtualBoxException.VirtualBoxException(
160 | "Cannot create %s - file already exists." % path)
161 | with VirtualBoxException.ExceptionHandler():
162 | # Despire the name of this method it returns an IMedium
163 | # instance
164 | imedium = cls._getVBox().createHardDisk(format, path)
165 | return cls(imedium)
166 |
167 | @classmethod
168 | def createWithStorage(cls, path, size,
169 | format=None, variant=None, wait=True):
170 | """Create a new hard disk at given location with given size (in MB).
171 |
172 | This is a wrapper around the create() and createBaseStorage() methods."""
173 | disk = cls.create(path, format)
174 | disk.createBaseStorage(size, variant, wait)
175 | return disk
176 |
177 | def getIMedium(self):
178 | """Return IMedium object."""
179 | return self._wrappedInstance
180 |
181 | def close(self):
182 | """Closes this medium."""
183 | self._wrappedInstance.close()
184 |
185 | def basename(self):
186 | """Return the basename of the location of the storage unit holding medium data."""
187 | return os.path.basename(self.location)
188 |
189 | def dirname(self):
190 | """Return the dirname of the location of the storage unit holding medium data."""
191 | return os.path.dirname(self.location)
192 |
193 | #
194 | # Internal string representations
195 | #
196 | def __str__(self):
197 | return self.name
198 |
199 | # IMedium apparently defines this and its method will sometimes
200 | # be called in preference to our __str__() method.
201 | def __unicode__(self):
202 | return self.name
203 |
204 | #
205 | # Instantiation of other methods
206 | #
207 | def cloneTo(self, target, variant=None, parent=None, wait=True):
208 | """Clone to the target hard drive.
209 |
210 | Returns Progress instance. If wait is True, does not return until process completes."""
211 | if variant is None:
212 | variant = Constants.MediumVariant_Standard
213 | with VirtualBoxException.ExceptionHandler():
214 | progress = self.getIMedium().cloneTo(target.getIMedium(),
215 | variant,
216 | parent)
217 | progress = Progress(progress)
218 | if wait:
219 | progress.waitForCompletion()
220 | return progress
221 |
222 | def createBaseStorage(self, size, variant=None, wait=True):
223 | """Create storage for the drive of the given size (in MB).
224 |
225 | Returns Progress instance. If wait is True, does not return until process completes."""
226 | if variant is None:
227 | variant = Constants.MediumVariant_Standard
228 | with VirtualBoxException.ExceptionHandler():
229 | progress = self.getIMedium().createBaseStorage(size, variant)
230 | progress = Progress(progress)
231 | if wait:
232 | progress.waitForCompletion()
233 | return progress
234 |
235 | #
236 | # Internal methods
237 | #
238 | @classmethod
239 | def _canonicalizeMediumPath(cls, path):
240 | """Given a path to a hard drive (or other medium) do any needed clean up."""
241 | # path must be absolute path
242 | return os.path.abspath(path)
243 |
244 | @classmethod
245 | def _getVBox(cls):
246 | """Return the VirtualBox object associated with this VirtualMachine."""
247 | return cls._manager.getIVirtualBox()
248 |
249 |
250 |
251 |
--------------------------------------------------------------------------------
/test/VirtualMachineTests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unittests for VirtualMachine"""
3 |
4 | from pyVBoxTest import pyVBoxTest, main
5 | from pyVBox import Constants
6 | from pyVBox import HardDisk
7 | from pyVBox import VirtualBoxException
8 | from pyVBox import VirtualBoxFileNotFoundException
9 | from pyVBox import VirtualBoxObjectNotFoundException
10 | from pyVBox import VirtualMachine
11 |
12 | from time import sleep
13 |
14 | class VirtualMachineTests(pyVBoxTest):
15 | """Test case for VirtualMachine"""
16 |
17 | def testOpen(self):
18 | """Test VirtualMachine.open()"""
19 | machine = VirtualMachine.open(self.testVMpath)
20 | self.assertNotEqual(None, machine.id)
21 | self.assertNotEqual(None, machine.name)
22 | self.assertEqual(True, machine.isDown())
23 |
24 | def testOpenNotFound(self):
25 | """Test VirtualMachine.open() with not found file"""
26 | self.assertRaises(
27 | VirtualBoxFileNotFoundException,
28 | VirtualMachine.open, self.bogusVMpath)
29 |
30 | def testLock(self):
31 | """Test VirtualMachine.lock()"""
32 | machine = VirtualMachine.open(self.testVMpath)
33 | machine.register()
34 | self.assertTrue(machine.isUnlocked())
35 | with machine.lock() as session:
36 | self.assertNotEqual(session, None)
37 | self.assertTrue(machine.isLocked())
38 | m2 = session.getMachine()
39 | self.assertNotEqual(m2, None)
40 | self.assertTrue(machine.isUnlocked())
41 | machine.unregister()
42 |
43 | def testRegister(self):
44 | """Test VirtualMachine.register() and related functions"""
45 | machine = VirtualMachine.open(self.testVMpath)
46 | machine.register()
47 | self.assertEqual(True, machine.isRegistered())
48 | m2 = VirtualMachine.find(machine.name)
49 | self.assertEqual(machine.id, m2.id)
50 | machine.unregister()
51 | self.assertEqual(False, machine.isRegistered())
52 |
53 | def testAttachDevice(self):
54 | """Test VirtualMachine.attachDevice()"""
55 | from pyVBox import DVD
56 | machine = VirtualMachine.open(self.testVMpath)
57 | machine.register()
58 | machine.attachDevice(DVD)
59 | machine.unregister()
60 |
61 | def testAttachMedium(self):
62 | """Test VirtualMachine.attachMedium() and related functions"""
63 | machine = VirtualMachine.open(self.testVMpath)
64 | machine.register()
65 | harddisk = HardDisk.open(self.testHDpath)
66 | machine.attachMedium(harddisk)
67 | mediums = machine.getAttachedMediums()
68 | self.assertEqual(1, len(mediums))
69 | self.assertEqual(mediums[0].deviceType, HardDisk)
70 | machine.detachMedium(harddisk)
71 | machine.unregister()
72 | harddisk.close()
73 |
74 | def testEject(self):
75 | """Test VirtualMachine.eject()"""
76 | machine = VirtualMachine.open(self.testVMpath)
77 | machine.register()
78 | self.assertEqual(True, machine.isRegistered())
79 | harddisk = HardDisk.open(self.testHDpath)
80 | machine.attachMedium(harddisk)
81 | machine.eject()
82 | self.assertEqual(False, machine.isRegistered())
83 | harddisk.close()
84 |
85 | def testPowerOn(self):
86 | """Test powering on a VM."""
87 | machine = VirtualMachine.open(self.testVMpath)
88 | machine.register()
89 | harddisk = HardDisk.open(self.testHDpath)
90 | machine.attachMedium(harddisk)
91 | machine.powerOn(type="vrdp")
92 | machine.waitUntilRunning()
93 | sleep(5)
94 | machine.powerOff(wait=True)
95 | machine.detachMedium(harddisk)
96 | harddisk.close()
97 | machine.unregister()
98 |
99 | def testSnapshot(self):
100 | """Test taking snapshot of a VM."""
101 | return # Issue: https://github.com/von/pyVBox/issues/5
102 | snapshotName = "Test Snapshot"
103 | machine = VirtualMachine.open(self.testVMpath)
104 | machine.register()
105 | self.assertEqual(None, machine.getCurrentSnapshot())
106 | # This sleep seems to keep takeSnapshot() from hanging
107 | # at least all of the time.
108 | # Issue: https://github.com/von/pyVBox/issues/5
109 | sleep(2)
110 | machine.takeSnapshot(snapshotName)
111 | snapshot = machine.getCurrentSnapshot()
112 | self.assertNotEqual(snapshot, None)
113 | self.assertEqual(snapshotName, snapshot.name)
114 | machine.deleteSnapshot(snapshot)
115 | self.assertEqual(None, machine.getCurrentSnapshot())
116 | machine.unregister()
117 |
118 | def testGet(self):
119 | """Test VirtualMachine.get() method"""
120 | machine = VirtualMachine.open(self.testVMpath)
121 | # Should fail since not registered yet
122 | self.assertRaises(
123 | VirtualBoxObjectNotFoundException,
124 | VirtualMachine.get, machine.id)
125 | machine.register()
126 | m2 = VirtualMachine.get(machine.id)
127 | self.assertNotEqual(None, m2)
128 | self.assertEqual(machine.id, m2.id)
129 | machine.unregister()
130 |
131 | def testGetAll(self):
132 | """Test VirtualMachine.getAll()"""
133 | # Make sure we have at least one vm
134 | machine = VirtualMachine.open(self.testVMpath)
135 | machine.register()
136 | vms = VirtualMachine.getAll()
137 | self.assertTrue(len(vms) > 0)
138 | self.assertTrue(isinstance(vms[0], VirtualMachine))
139 | machine.unregister()
140 |
141 | def testGetOSType(self):
142 | """Test getOSType() method"""
143 | machine = VirtualMachine.open(self.testVMpath)
144 | osType = machine.getOSType()
145 | self.assertNotEqual(None, osType)
146 | self.assertNotEqual(None, osType.familyId)
147 | self.assertNotEqual(None, osType.familyDescription)
148 | self.assertNotEqual(None, osType.id)
149 | self.assertNotEqual(None, osType.description)
150 |
151 | def testGetHardDrives(self):
152 | """Test VirtualMachine.getHardDrives() method"""
153 | machine = VirtualMachine.open(self.testVMpath)
154 | machine.register()
155 | # Attach something so it's interesting
156 | harddisk = HardDisk.open(self.testHDpath)
157 | machine.attachMedium(harddisk)
158 | mediums = machine.getAttachedMediums()
159 | self.assertEqual(1, len(mediums))
160 | self.assertEqual(mediums[0].deviceType, HardDisk)
161 | hds = machine.getHardDrives()
162 | self.assertNotEqual(hds, None)
163 | self.assertTrue(isinstance(hds, list))
164 | self.assertEqual(len(hds), 1)
165 | machine.detachMedium(harddisk)
166 | machine.unregister()
167 |
168 | def testCreate(self):
169 | """Test VirtualMachine.create() method"""
170 | # If VM already exists and we don't specify forceOverwrite=True
171 | # this will raise a VirtualBoxFileError
172 | machine = VirtualMachine.create("CreateTestVM", "Ubuntu",
173 | forceOverwrite=True)
174 | # Clean up
175 | machine.unregister()
176 | machine.delete()
177 |
178 | def testGetStorageControllers(self):
179 | """Test VirtualMachine methods for getting StorageControllers"""
180 | machine = VirtualMachine.open(self.testVMpath)
181 | controllers = machine.getStorageControllers()
182 | self.assertNotEqual(None, controllers)
183 | for controller in controllers:
184 | c = machine.getStorageControllerByName(controller.name)
185 | self.assertNotEqual(None, c)
186 | c = machine.getStorageControllerByInstance(controller.instance)
187 | self.assertNotEqual(None, c)
188 | self.assertEqual(True,
189 | machine.doesStorageControllerExist(controller.name))
190 | def testAddStorageController(self):
191 | """Test adding and removing of StorageController to a VirtualMachine"""
192 | # Currently the removeStorageController() method is failing with
193 | # an 'Operation aborted' and the test VM fails to boot if I leave
194 | # the added storage controllers, which messes up subsequent tests.
195 | # Issue: https://github.com/von/pyVBox/issues/2
196 | controllerName="TestController"
197 | machine = VirtualMachine.open(self.testVMpath)
198 | machine.register()
199 | controller = machine.addStorageController(Constants.StorageBus_SCSI,
200 | name=controllerName)
201 | self.assertNotEqual(None, controller)
202 | self.assertEqual(controllerName, controller.name)
203 | self.assertEqual(True,
204 | machine.doesStorageControllerExist(controller.name))
205 | machine.removeStorageController(controller.name)
206 | # Trying to use controller.name after remove fails.
207 | # Not sure if that's a bug or not.
208 | self.assertEqual(False,
209 | machine.doesStorageControllerExist(controllerName))
210 |
211 | def testSetAttr(self):
212 | """Set setting of VirtualMachine attributes"""
213 | machine = VirtualMachine.open(self.testVMpath)
214 | machine.register()
215 | with machine.lock() as session:
216 | mutableMachine = session.getMachine()
217 | # Double memory and make sure it persists
218 | newMemorySize = machine.memorySize * 2
219 | mutableMachine.memorySize = newMemorySize
220 | session.saveSettings()
221 | self.assertEqual(newMemorySize, mutableMachine.memorySize)
222 | machine.unregister()
223 | machine2 = VirtualMachine.open(self.testVMpath)
224 | self.assertEqual(newMemorySize, machine2.memorySize)
225 |
226 | def testClone(self):
227 | """Test VirtualMachine.clone() method"""
228 | machine = VirtualMachine.open(self.testVMpath)
229 | newMachine = machine.clone(self.cloneVMname)
230 | self.assertEqual(self.cloneVMname, newMachine.name)
231 | self.assertEqual(machine.description, newMachine.description)
232 | self.assertEqual(machine.CPUCount, newMachine.CPUCount)
233 | self.assertEqual(machine.memorySize, newMachine.memorySize)
234 | self.assertEqual(machine.VRAMSize, newMachine.VRAMSize)
235 | self.assertEqual(machine.accelerate3DEnabled,
236 | newMachine.accelerate3DEnabled)
237 | self.assertEqual(machine.accelerate2DVideoEnabled,
238 | newMachine.accelerate2DVideoEnabled)
239 | self.assertEqual(machine.monitorCount,
240 | newMachine.monitorCount)
241 | with newMachine.lock() as session:
242 | controllers = machine.getStorageControllers()
243 | newControllers = newMachine.getStorageControllers()
244 | self.assertEqual(len(controllers), len(newControllers))
245 | # Todo: compare individual controllers
246 | # Clean up
247 | newMachine.unregister()
248 | newMachine.delete()
249 |
250 | if __name__ == '__main__':
251 | main()
252 |
253 |
--------------------------------------------------------------------------------
/utils/pyvbox.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """pyVBox utility to control VirtualBox VMs.
3 | """
4 |
5 | from pyVBox import HardDisk
6 | from pyVBox import VirtualBox
7 | from pyVBox import VirtualBoxException
8 | from pyVBox import VirtualMachine
9 |
10 | import atexit
11 | import optparse
12 | import os.path
13 | import sys
14 | import traceback
15 |
16 | #----------------------------------------------------------------------
17 | #
18 | # Output functions
19 | #
20 |
21 | # Default = 1, 0 = quiet, 2 = verbose
22 | verbosityLevel = 1
23 |
24 | def errorMsg(msg):
25 | sys.stderr.write(msg + "\n")
26 |
27 | def handle_exception(e, msg=None):
28 | sys.stderr.write("Error: ")
29 | if msg is not None:
30 | sys.stderr.write(msg + ": ")
31 | sys.stderr.write(str(e) + "\n")
32 | if verbosityLevel > 1:
33 | traceback.print_exc()
34 |
35 | def message(msg):
36 | if verbosityLevel > 0:
37 | sys.stdout.write(msg + "\n")
38 | sys.stdout.flush()
39 |
40 | def verboseMsg(msg):
41 | if verbosityLevel > 1:
42 | sys.stdout.write(msg + "\n")
43 | sys.stdout.flush()
44 |
45 |
46 | def show_progress(progress, prefix="Progess: "):
47 | """Given a Progress instance, display progress to user as percent.
48 |
49 | The string prefix will precent the percentage.
50 | If running in quiet mode, displays nothing."""
51 | if verbosityLevel > 0:
52 | try:
53 | while not progress.completed:
54 | print "%s%2d%%\r" % (prefix, progress.percent),
55 | sys.stdout.flush()
56 | # Wait one second
57 | progress.waitForCompletion(timeout=1000)
58 | except KeyboardInterrupt:
59 | print "Interrupted."
60 | else:
61 | # Print one last time with carriage return
62 | print "%s%2d%%" % (prefix, progress.percent)
63 | else:
64 | progress.waitForCompletion()
65 |
66 | def print_vm(vm):
67 | """Given a VM instance, display all the information about it."""
68 | print "VM: %s" % vm.name
69 | print " Id: %s" % vm.id
70 | osType = vm.getOSType()
71 | print " OS: %s" % osType.description
72 | print " CPU count: %d" % vm.CPUCount
73 | print " RAM: %d MB" % vm.memorySize
74 | print " VRAM: %d MB" % vm.VRAMSize
75 | print " Monitors: %d" % vm.monitorCount
76 | attachments = vm.getMediumAttachments()
77 | for attachment in attachments:
78 | medium = attachment.medium
79 | type = attachment.type
80 | print " Device: %s" % type
81 | if medium:
82 | print " Medium: %s" % medium.name
83 | print " Id: %s" % medium.id
84 | print " Location: %s" % medium.location
85 | print " Format: %s" % medium.format
86 | print " Size: %s" % medium.size
87 | print " Controller: %s Port: %d" % (attachment.controller,
88 | attachment.port)
89 | snapshot = vm.getCurrentSnapshot()
90 | if snapshot:
91 | print " Current Snapshot: %s" % snapshot.name
92 |
93 | #----------------------------------------------------------------------
94 | #
95 | # Commands
96 |
97 | class Command:
98 | """Base class for all commands."""
99 | usage = " 0:
361 | for name in args:
362 | try:
363 | cmd = Command.lookup_command_by_name(name)
364 | message("%10s: %s" % (name, cmd.__doc__))
365 | message("%10s usage: %s" % ("", cmd.usage))
366 | except KeyError, e:
367 | message("Unknown command '%s'" % name)
368 | else:
369 | message("Available commands:")
370 | commands = Command.registered_commands.keys()
371 | commands.sort()
372 | # What's the length of the longer command name?
373 | longestNameLen = max(map(len, commands))
374 | for name in commands:
375 | cmd = Command.lookup_command_by_name(name)
376 | message("%*s: %s" % (longestNameLen, name, cmd.__doc__))
377 |
378 | Command.register_command("help", HelpCommand)
379 |
380 | class ListCommand(Command):
381 | """Display a list of all available virtual machines"""
382 | usage = "list"
383 |
384 | @classmethod
385 | def invoke(cls, args):
386 | """Invoke the command. Return exit code for program."""
387 | vms = VirtualMachine.getAll()
388 | for vm in vms:
389 | try:
390 | print vm.name
391 | except VirtualBoxException as e:
392 | if verbosityLevel > 1:
393 | errorMsg("Unknown machine: %s"%e)
394 | return 0
395 |
396 | Command.register_command("list", ListCommand)
397 |
398 | class OSTypesCommand(Command):
399 | """Display all the available guest OS types"""
400 | usage = "guestOSTypes"
401 |
402 | @classmethod
403 | def invoke(cls, args):
404 | """Invoke the command. Return exit code for program."""
405 | osTypes = VirtualBox().getGuestOSTypes()
406 | for ostype in osTypes:
407 | print "%s (%s)" % (ostype.description, ostype.id)
408 | return 0
409 |
410 | Command.register_command("guestOSTypes", OSTypesCommand)
411 |
412 | class PauseCommand(Command):
413 | """Pause a running VM"""
414 | usage = "pause "
415 |
416 | @classmethod
417 | def invoke(cls, args):
418 | """Invoke the command. Return exit code for program."""
419 | if len(args) == 0:
420 | raise Exception("Missing virtual machine name argument");
421 | vm = VirtualMachine.find(args.pop(0))
422 | vm.pause()
423 | return 0
424 |
425 | Command.register_command("pause", PauseCommand)
426 |
427 | class RegisterCommand(Command):
428 | """Register a VM"""
429 | usage = "register [...]"
430 |
431 | @classmethod
432 | def invoke(cls, args):
433 | """Invoke the command. Return exit code for program."""
434 | if len(args) == 0:
435 | raise Exception("Missing virtual machine filename argument");
436 | for filename in args:
437 | vm = VirtualMachine.open(args.pop(0))
438 | if vm.isRegistered():
439 | errorMsg("VM \"%s\" is already registered." % vm)
440 | else:
441 | verboseMsg("Registering VM %s" % vm)
442 | vm.register()
443 | return 0
444 |
445 | Command.register_command("register", RegisterCommand)
446 |
447 | class ResumeCommand(Command):
448 | """Resume a paused VM"""
449 | usage = "resume "
450 |
451 | @classmethod
452 | def invoke(cls, args):
453 | """Invoke the command. Return exit code for program."""
454 | if len(args) == 0:
455 | raise Exception("Missing virtual machine name argument");
456 | vm = VirtualMachine.find(args.pop(0))
457 | vm.resume()
458 | return 0
459 |
460 | Command.register_command("resume", ResumeCommand)
461 |
462 | class SnapshotCommand(Command):
463 | """Snapshot a VM"""
464 | usage = "snapshot []"
465 |
466 | @classmethod
467 | def invoke(cls, args):
468 | """Invoke the command. Return exit code for program."""
469 | if len(args) == 0:
470 | raise Exception("Missing VM name")
471 | vm = VirtualMachine.find(args.pop(0))
472 | if len(args) == 0:
473 | raise Exception("Missing snapshot name")
474 | name = args.pop(0)
475 | description = None
476 | if len(args) > 0:
477 | description = args.pop(0)
478 | vm.takeSnapshot(name, description)
479 |
480 | Command.register_command("snapshot", SnapshotCommand)
481 |
482 | class StartCommand(Command):
483 | """Start a VM"""
484 | usage = "start "
485 |
486 | @classmethod
487 | def invoke(cls, args):
488 | """Invoke the command. Return exit code for program."""
489 | mode = "gui"
490 | if len(args) == 0:
491 | raise Exception("Missing virtual machine name argument");
492 | vm = VirtualMachine.find(args.pop(0))
493 | vm.powerOn(type=mode)
494 |
495 | Command.register_command("start", StartCommand)
496 |
497 | class UnregisterCommand(Command):
498 | """Unregister a VM"""
499 | usage = "unregister [...]"
500 |
501 | @classmethod
502 | def invoke(cls, args):
503 | """Invoke the command. Return exit code for program."""
504 | if len(args) == 0:
505 | raise Exception("Missing virtual machine name argument");
506 | for filename in args:
507 | vm = VirtualMachine.find(args.pop(0))
508 | if vm.isRegistered():
509 | verboseMsg("Unregistering VM %s" % vm)
510 | vm.unregister()
511 | else:
512 | errorMsg("VM \"%s\" is not registered." % vm)
513 | return 0
514 |
515 | Command.register_command("unregister", UnregisterCommand)
516 |
517 | class VMCommand(Command):
518 | """Display information about one or more VMs"""
519 | usage = "vm []"
520 |
521 | @classmethod
522 | def invoke(cls, args):
523 | if len(args) == 0:
524 | vms = VirtualMachine.getAll()
525 | verboseMsg("Registered VMs:")
526 | for vm in vms:
527 | try:
528 | print "\t%s" % vm
529 | except Exception as e:
530 | errorMsg("Could not display VM: %s" % str(e))
531 | else:
532 | for vmName in args:
533 | try:
534 | vm = VirtualMachine.find(vmName)
535 | print_vm(vm)
536 | except Exception as e:
537 | errorMsg("Could not display information about VM \"%s\": %s" % (vmName, str(e)))
538 |
539 | Command.register_command("vm", VMCommand)
540 |
541 | #----------------------------------------------------------------------
542 |
543 | def main(argv=None):
544 | global verbosityLevel
545 |
546 | if argv is None:
547 | argv = sys.argv
548 |
549 | usage = "usage: %prog [options] []"
550 | version= "%prog 1.0"
551 | parser = optparse.OptionParser(usage=usage, version=version)
552 | parser.add_option("-q", "--quiet", dest="verbosityLevel",
553 | action="store_const", const=0,
554 | help="surpress all messages")
555 | parser.add_option("-v", "--verbose", dest="verbosityLevel",
556 | action="store_const", const=2,
557 | help="be verbose")
558 | (options, args) = parser.parse_args()
559 | if len(args) < 1:
560 | parser.error("missing command")
561 | commandStr = args.pop(0)
562 |
563 | if options.verbosityLevel != None:
564 | verbosityLevel = options.verbosityLevel
565 | verboseMsg("Setting verbosity level to %d" % verbosityLevel)
566 |
567 | try:
568 | command = Command.lookup_command_by_name(commandStr)
569 | except Exception, e:
570 | parser.error("Unrecognized command \"%s\"" % commandStr)
571 | return 1
572 |
573 | try:
574 | status = command.invoke(args)
575 | except Exception, e:
576 | handle_exception(e)
577 | return 1
578 | return status
579 |
580 | if __name__ == "__main__":
581 | sys.exit(main())
582 |
--------------------------------------------------------------------------------
/pyVBox/VirtualMachine.py:
--------------------------------------------------------------------------------
1 | """Wrapper around IMachine object"""
2 |
3 | from HardDisk import HardDisk
4 | from Medium import Medium
5 | from MediumAttachment import MediumAttachment
6 | from Progress import Progress
7 | from Session import Session
8 | from Snapshot import Snapshot
9 | from StorageController import StorageController
10 | from VirtualBox import VirtualBox
11 | import VirtualBoxException
12 | from VirtualBoxManager import Constants, VirtualBoxManager
13 | from Wrapper import Wrapper
14 |
15 | from contextlib import contextmanager
16 | import os
17 | import os.path
18 |
19 | class VirtualMachine(Wrapper):
20 | # Properties directly inherited from IMachine
21 | _passthruProperties = [
22 | "accelerate2DVideoEnabled",
23 | "accelerate3DEnabled",
24 | "accessible",
25 | "CPUCount",
26 | "currentStateModified",
27 | "description",
28 | "guestPropertyNotificationPatterns",
29 | "HardwareVersion",
30 | "hardwareUUID",
31 | "id",
32 | "lastStateChange",
33 | "lockMachine",
34 | "logFolder",
35 | "memorySize",
36 | "monitorCount",
37 | "name",
38 | "OSTypeId",
39 | "sessionPid",
40 | "sessionState",
41 | "sessionType",
42 | "settingsFilePath",
43 | "settingsModified",
44 | "snapshotCount",
45 | "snapshotFolder",
46 | "state",
47 | "stateFilePath",
48 | "statisticsUpdateInterval",
49 | "teleporterAddress",
50 | "teleporterEnabled",
51 | "teleporterPassword",
52 | "teleporterPort",
53 | "unregister",
54 | "VRAMSize",
55 | ]
56 |
57 | _manager = VirtualBoxManager()
58 | _vbox = VirtualBox()
59 |
60 | def __init__(self, machine, session=None):
61 | """Return a VirtualMachine wrapper around given IMachine instance"""
62 | self._wrappedInstance = machine
63 |
64 | def __del__(self):
65 | pass
66 |
67 | def __str__(self):
68 | return self.name
69 |
70 | #
71 | # Top-level controls
72 | #
73 | def pause(self, wait=False):
74 | """Pause a running VM.
75 |
76 | If wait is True, then wait until machine is actually paused before returning."""
77 | with self.lock() as session:
78 | with VirtualBoxException.ExceptionHandler():
79 | session.console.pause()
80 | # XXX Note sure if we need a lock for this or not
81 | if wait:
82 | self.waitUntilPaused()
83 |
84 | def resume(self):
85 | """Resume a paused VM."""
86 | with self.lock() as session:
87 | with VirtualBoxException.ExceptionHandler():
88 | session.console.resume()
89 |
90 | def powerOff(self, wait=False):
91 | """Power off a running VM.
92 |
93 | If wait is True, then wait for power down and session closureto complete."""
94 | with self.lock() as session:
95 | with VirtualBoxException.ExceptionHandler():
96 | session.console.powerDown()
97 | # XXX Not sure we need a lock for the following
98 | if wait:
99 | self.waitUntilDown()
100 | self.waitUntilUnlocked()
101 |
102 | def powerOn(self, type="gui", env=""):
103 | """Spawns a new process that executes a virtual machine.
104 |
105 | This is spawning a "remote session" in VirtualBox terms."""
106 | # TODO: Add a wait argument
107 | if not self.isRegistered():
108 | raise VirtualBoxException.VirtualBoxInvalidVMStateException(
109 | "VM is not registered")
110 | with VirtualBoxException.ExceptionHandler():
111 | iMachine = self.getIMachine()
112 | session = Session.create()
113 | iprogress = iMachine.launchVMProcess(session.getISession(),
114 | type, env)
115 | progress = Progress(iprogress)
116 | progress.waitForCompletion()
117 | session.unlockMachine()
118 |
119 | def eject(self):
120 | """Do what ever it takes to unregister the VM"""
121 | if not self.isRegistered():
122 | # Nothing to do
123 | return
124 | if self.isRunning():
125 | self.powerOff(wait=True)
126 | self.unregister(cleanup_mode=Constants.CleanupMode_DetachAllReturnNone)
127 |
128 | def delete(self):
129 | """Delete the VM.
130 |
131 | VM must be locked or unregistered"""
132 | with VirtualBoxException.ExceptionHandler():
133 | iMachine = self.getIMachine()
134 | iprogress = iMachine.delete(None)
135 | progress = Progress(iprogress)
136 | progress.waitForCompletion()
137 |
138 | #
139 | # Creation methods
140 | #
141 |
142 | @classmethod
143 | def open(cls, path):
144 | """Opens a virtual machine from the existing settings file.
145 |
146 | Note that calling open() on a VM that is already registered will
147 | throw a VirtualBoxFileNotFoundException except.
148 |
149 | Throws VirtualBoxFileNotFoundException if file not found."""
150 | with VirtualBoxException.ExceptionHandler():
151 | path = cls._canonicalizeVMPath(path)
152 | machine = cls._vbox.openMachine(path)
153 | return VirtualMachine(machine)
154 |
155 | @classmethod
156 | def find(cls, nameOrId):
157 | """Attempts to find a virtual machine given its name or UUID."""
158 | with VirtualBoxException.ExceptionHandler():
159 | machine = cls._vbox.findMachine(nameOrId)
160 | return VirtualMachine(machine)
161 |
162 | @classmethod
163 | def get(cls, id):
164 | """Attempts to find a virtual machine given its UUID."""
165 | return cls.find(id)
166 |
167 | @classmethod
168 | def create(cls, name, osTypeId, settingsFile=None, id=None, register=True,
169 | forceOverwrite=False):
170 | """Create a new virtual machine with the given name and osType.
171 |
172 | If settingsFile is not None, it should be a path to use instead
173 | of the default for the settings file.
174 |
175 | If id is not None, it will be used as the UUID of the
176 | machine. Otherwise one will be automatically generated.
177 |
178 | If register is True, register machine after creation."""
179 | with VirtualBoxException.ExceptionHandler():
180 | machine = cls._vbox.createMachine(settingsFile,
181 | name,
182 | osTypeId,
183 | id,
184 | forceOverwrite)
185 | vm = VirtualMachine(machine)
186 | vm.saveSettings()
187 | if register:
188 | vm.register()
189 | return vm
190 |
191 | def clone(self, name, settingsFile=None, id=None, register=True,
192 | description=None):
193 | """Clone this virtual machine as new VM with given name.
194 |
195 | Clones basic properties of machine plus any storage
196 | controllers. Does not clone any attached storage.
197 |
198 | If settingsFile is not None, it should be a path to use instead
199 | of the default for the settingsFile
200 |
201 | If id is not None, it will be used as the UUID of the
202 | new machine. Otherwise one will be automatically generated.
203 |
204 | If register is True, register new machine after creation.
205 |
206 | If description is None, copy description from source, otherwise use description."""
207 | vm = VirtualMachine.create(name,
208 | self.OSTypeId,
209 | settingsFile=settingsFile,
210 | id=id,
211 | # Once we register, we cannot make
212 | # changes without opening a
213 | # session, so defer any
214 | # registration.
215 | register=False)
216 | if description:
217 | vm.description = description
218 | else:
219 | vm.description = self.description
220 | vm.CPUCount = self.CPUCount
221 | vm.memorySize = self.memorySize
222 | vm.VRAMSize = self.VRAMSize
223 | vm.accelerate3DEnabled = self.accelerate3DEnabled
224 | vm.accelerate2DVideoEnabled = self.accelerate2DVideoEnabled
225 | vm.monitorCount = self.monitorCount
226 | controllers = self.getStorageControllers()
227 | vm.register()
228 | for controller in controllers:
229 | vm.addStorageController(controller.bus,
230 | name = controller.name)
231 | if not register:
232 | clone.unregister()
233 | return vm
234 |
235 | @classmethod
236 | def getAll(cls):
237 | """Return an array of all known virtual machines"""
238 | return [VirtualMachine(vm) for vm in cls._vbox.machines]
239 |
240 | #
241 | # Registration methods
242 | #
243 |
244 | def register(self):
245 | """Registers the machine within this VirtualBox installation."""
246 | with VirtualBoxException.ExceptionHandler():
247 | self._vbox.registerMachine(self.getIMachine())
248 |
249 | def unregister(self,
250 | cleanup_mode=Constants.CleanupMode_DetachAllReturnNone):
251 | """Unregisters the machine previously registered using register()."""
252 | with VirtualBoxException.ExceptionHandler():
253 | machine = self.getIMachine()
254 | machine.unregister(cleanup_mode)
255 |
256 | def isRegistered(self):
257 | """Is this virtual machine registered?"""
258 | from VirtualBoxException import VirtualBoxObjectNotFoundException
259 | try:
260 | VirtualMachine.get(self.id)
261 | registered = True
262 | except VirtualBoxObjectNotFoundException, e:
263 | registered = False
264 | except Exception, e:
265 | raise
266 | return registered
267 |
268 | #
269 | # Snapshot methods
270 | #
271 | def getCurrentSnapshot(self):
272 | """Returns current snapshot of this machine or None if machine currently has no snapshots"""
273 | imachine = self.getIMachine()
274 | if imachine.currentSnapshot is None:
275 | return None
276 | return Snapshot(imachine.currentSnapshot)
277 |
278 | def takeSnapshot(self, name, description=None, wait=True):
279 | """Saves the current execution state and all settings of the machine and creates differencing images for all normal (non-independent) media.
280 |
281 | Returns Progress instance. If wait is True, does not return until process completes."""
282 | assert(name is not None)
283 | with self.lock() as session:
284 | with VirtualBoxException.ExceptionHandler():
285 | iprogress = session.console.takeSnapshot(name, description)
286 | progress = Progress(iprogress)
287 | # XXX Not sure if we need a lock for this or not
288 | if wait:
289 | progress.waitForCompletion()
290 | return progress
291 |
292 | def deleteSnapshot(self, snapshot, wait=True):
293 | """Deletes the specified snapshot.
294 |
295 | Returns Progress instance. If wait is True, does not return until process completes."""
296 | assert(snapshot is not None)
297 | with self.lock() as session:
298 | with VirtualBoxException.ExceptionHandler():
299 | iprogress = session.console.deleteSnapshot(snapshot.id)
300 | progress = Progress(iprogress)
301 | # XXX Not sure if we need a lock for this or not
302 | if wait:
303 | progress.waitForCompletion()
304 | return progress
305 |
306 | #
307 | # Attribute getters
308 | #
309 |
310 | def getIMachine(self):
311 | """Return wrapped IMachine instance."""
312 | return self._wrappedInstance
313 |
314 | def getOSType(self):
315 | """Returns an object describing the specified guest OS type."""
316 | with VirtualBoxException.ExceptionHandler():
317 | imachine = self.getIMachine()
318 | osType = self._vbox.getGuestOSType(imachine.OSTypeId)
319 | return osType
320 |
321 | #
322 | # Locking and unlocking
323 | #
324 |
325 | @contextmanager
326 | def lock(self, type=Constants.LockType_Shared):
327 | """Contextmanager yielding a session to a locked machine.
328 |
329 | Machine must be registered."""
330 | session = Session.create()
331 | with VirtualBoxException.ExceptionHandler():
332 | self.getIMachine().lockMachine(session.getISession(), type)
333 | try:
334 | session._setMachine(VirtualMachine(session.getIMachine()))
335 | yield session
336 | finally:
337 | session.unlockMachine(wait=True)
338 |
339 | def isLocked(self):
340 | """Does the machine have an open session?"""
341 | state = self.sessionState
342 | return ((state == Constants.SessionState_Locked) or
343 | (state == Constants.SessionState_Spawning) or
344 | (state == Constants.SessionState_Unlocking))
345 |
346 | def isUnlocked(self):
347 | """Does the VM not have an open session."""
348 | state = self.sessionState
349 | return ((state == Constants.SessionState_Null) or
350 | (state == Constants.SessionState_Unlocked))
351 |
352 | def waitUntilUnlocked(self):
353 | """Wait until VM is unlocked"""
354 | while not self.isUnlocked():
355 | self.waitForEvent()
356 |
357 | #
358 | # Attach methods
359 | #
360 |
361 | def attachMedium(self, medium):
362 | """Attachs a medium.."""
363 | self.attachDevice(medium.deviceType, medium)
364 |
365 | def attachDevice(self, device, medium=None):
366 | """Attaches a Device and optionally a Medium."""
367 | imedium = medium.getIMedium() if medium else None
368 | with self.lock() as session:
369 | with VirtualBoxException.ExceptionHandler():
370 | # XXX following code needs to be smarter and find appropriate
371 | # attachment point
372 | storageControllers = self._getStorageControllers()
373 | storageController = storageControllers[0]
374 | controllerPort = 0
375 | deviceNum = 0
376 | session.getIMachine().attachDevice(storageController.name,
377 | controllerPort,
378 | deviceNum,
379 | device.type,
380 | imedium)
381 | session.saveSettings()
382 |
383 | def detachMedium(self, device):
384 | """Detach the medium from the machine."""
385 | with self.lock() as session:
386 | with VirtualBoxException.ExceptionHandler():
387 | attachment = self._findMediumAttachment(device)
388 | session.getIMachine().detachDevice(attachment.controller,
389 | attachment.port,
390 | attachment.device)
391 | session.saveSettings()
392 |
393 | def detachAllMediums(self):
394 | """Detach all mediums from the machine."""
395 | with self.lock() as session:
396 | with VirtualBoxException.ExceptionHandler():
397 | attachments = self._getMediumAttachments()
398 | for attachment in attachments:
399 | session.getIMachine().detachDevice(attachment.controller,
400 | attachment.port,
401 | attachment.device)
402 | session.saveSettings()
403 |
404 | def getAttachedMediums(self):
405 | """Return array of attached Medium instances."""
406 | with self.lock() as session:
407 | mediums = []
408 | with VirtualBoxException.ExceptionHandler():
409 | attachments = self._getMediumAttachments()
410 | attachments = filter(lambda a: a.medium is not None,
411 | attachments)
412 | mediums = [Medium(a.medium) for a in attachments]
413 | return mediums
414 |
415 | def getHardDrives(self):
416 | """Return array of Medium instances representing attached HardDrives."""
417 | return filter(lambda m: m.deviceType == HardDisk,
418 | self.getAttachedMediums())
419 |
420 | #
421 | # MediumAttachment methods
422 | #
423 | def getMediumAttachments(self):
424 | """Return array of MediumAttachments"""
425 | return [MediumAttachment(ma) for ma in self._getMediumAttachments()]
426 |
427 | def _findMediumAttachment(self, device):
428 | """Given a device, find the IMediumAttachment object associated with its attachment on this machine."""
429 | assert(device is not None)
430 | mediumAttachments = self._getMediumAttachments()
431 | for attachment in mediumAttachments:
432 | # medium can be Null for removable devices
433 | if attachment.medium is not None:
434 | if attachment.medium.id == device.id:
435 | return attachment
436 | raise VirtualBoxException.VirtualBoxPluggableDeviceManagerError(
437 | "No attachment for device \"%s\" on VM \"%s\" found" % (device,
438 | self))
439 | def _getMediumAttachments(self):
440 | """Return the array of medium attachements on this virtual machine."""
441 | return self._getArray('mediumAttachments')
442 |
443 | #
444 | # StorageController methods
445 | #
446 |
447 | def getStorageControllers(self):
448 | """Return array of StorageControllers attached to this VM"""
449 | return [StorageController(c) for c in self._getStorageControllers()]
450 |
451 | def getStorageControllerByName(self, name):
452 | """Return the StorageController with the given name"""
453 | with VirtualBoxException.ExceptionHandler():
454 | controller = self.getIMachine().getStorageControllerByName(name)
455 | return StorageController(controller)
456 |
457 | def getStorageControllerByInstance(self, instance):
458 | """Return the StorageController with the given instance number"""
459 | with VirtualBoxException.ExceptionHandler():
460 | controller = self.getIMachine().getStorageControllerByInstance(instance)
461 | return StorageController(controller)
462 |
463 | def removeStorageController(self, name):
464 | """Removes a storage controller from the machine."""
465 | with self.lock() as session:
466 | with VirtualBoxException.ExceptionHandler():
467 | mutableMachine = session.getMachine()
468 | mutableMachine.getIMachine().removeStorageController(name)
469 | session.saveSettings()
470 |
471 | def doesStorageControllerExist(self, name):
472 | """Return boolean indicating if StorageController with given name exists"""
473 | exists = False
474 | try:
475 | controller = self.getStorageControllerByName(name)
476 | except VirtualBoxException.VirtualBoxObjectNotFoundException, e:
477 | exists = False
478 | except:
479 | raise
480 | else:
481 | exists = True
482 | return exists
483 |
484 | def addStorageController(self, type, name=None):
485 | """Add a storage controller to the virtual machine
486 |
487 | type should be the bus type of the new controller. Must be one of Constants.StorageBus_IDE, Constants.StorageBus_SATA, Constants.StorageBus_SCSI, or Constants.StorageBus_Floppy
488 |
489 | name should be the name of the storage controller. If None, a name will be assigned.
490 |
491 | Returns StorageController instance for new controller.
492 | """
493 | if name is None:
494 | name = self._getNewStorageControllerName(type)
495 | with self.lock() as session:
496 | with VirtualBoxException.ExceptionHandler():
497 | mutableMachine = session.getMachine()
498 | controller = mutableMachine.getIMachine().addStorageController(name, type)
499 | session.saveSettings()
500 | return StorageController(controller)
501 |
502 | def _getNewStorageControllerName(self, type):
503 | """Choose a name for a new StorageController of the given type.
504 |
505 | Takes a string describing the controller type and adds an number to it to uniqify it if needed."""
506 | baseNames = {
507 | Constants.StorageBus_IDE : "IDE Controller",
508 | Constants.StorageBus_SATA : "SATA Controller",
509 | Constants.StorageBus_SCSI : "SCSI Controller",
510 | Constants.StorageBus_Floppy : "Floppy Controller"
511 | }
512 | if not baseNames.has_key(type):
513 | # Todo: Use correct argument type here
514 | raise Exception("Invalid type '%d'" % type)
515 | count = 1
516 | name = baseNames[type]
517 | while self.doesStorageControllerExist(name):
518 | count += 1
519 | name = "%s %d" % (baseNames[type], count)
520 | return name
521 |
522 | #
523 | # Settings functions
524 | #
525 |
526 | def saveSettings(self):
527 | """Saves any changes to machine settings made since the session has been opened or a new machine has been created, or since the last call to saveSettings or discardSettings."""
528 | with VirtualBoxException.ExceptionHandler():
529 | self.getIMachine().saveSettings()
530 |
531 | #
532 | # Monitoring methods
533 | #
534 |
535 | def waitForEvent(self):
536 | self._getManager().waitForEvents()
537 |
538 | def waitUntilRunning(self):
539 | """Wait until machine is running."""
540 | while not self.isRunning():
541 | self.waitForEvent()
542 |
543 | def waitUntilDown(self):
544 | """Wait until machine is down (cleanly or not)."""
545 | while not self.isDown():
546 | self.waitForEvent()
547 |
548 | def isDown(self):
549 | """Is machine down (PoweredOff, Aborted)?"""
550 | state = self.state
551 | if ((state == Constants.MachineState_Aborted) or
552 | (state == Constants.MachineState_PoweredOff)):
553 | return True
554 | return False
555 |
556 | def isRunning(self):
557 | """Is machine Running?"""
558 | state = self.state
559 | if (state == Constants.MachineState_Running):
560 | return True
561 | return False
562 |
563 | def isPaused(self):
564 | """Is machine Paused?"""
565 | state = self.state
566 | if (state == Constants.MachineState_Paused):
567 | return True
568 | return Fals
569 |
570 | def waitUntilPaused(self):
571 | """Wait until machine is paused."""
572 | while not self.isPaused():
573 | self.waitForEvent()
574 |
575 | #
576 | # Internal utility functions
577 | #
578 |
579 | @classmethod
580 | def _canonicalizeVMPath(cls, path):
581 | """Given a path to a VM do any needed clean up."""
582 | # path must be absolute path
583 | return os.path.abspath(path)
584 |
585 |
586 |
587 | #
588 | # Internal attribute getters
589 | #
590 |
591 | def _getArray(self, arrayName):
592 | """Return the array identified by the given name on this virtual machine."""
593 | return self._getManager().getArray(self.getIMachine(), arrayName)
594 |
595 | def _getManager(self):
596 | """Return the IVirtualBoxManager object associated with this VirtualMachine."""
597 | return self._manager
598 |
599 | def _getStorageControllers(self):
600 | """Return the array of storage controllers associated with this virtual machine."""
601 | return self._getArray('storageControllers')
602 |
603 | # Simple implementation of IConsoleCallback
604 | class VirtualMachineMonitor:
605 | def __init__(self, vm):
606 | self.vm = vm
607 |
608 | def onMousePointerShapeChange(self, visible, alpha, xHot, yHot,
609 | width, height, shape):
610 | pass
611 |
612 | def onMouseCapabilityChange(self, supportsAbsolute, needsHostCursor):
613 | pass
614 |
615 | def onKeyboardLedsChange(self, numLock, capsLock, scrollLock):
616 | pass
617 |
618 | def onStateChange(self, state):
619 | pass
620 |
621 | def onAdditionsStateChange(self):
622 | pass
623 |
624 | def onNetworkAdapterChange(self, adapter):
625 | pass
626 |
627 | def onSerialPortChange(self, port):
628 | pass
629 |
630 | def onParallelPortChange(self, port):
631 | pass
632 |
633 | def onStorageControllerChange(self):
634 | pass
635 |
636 | def onMediumChange(self, attachment):
637 | pass
638 |
639 | def onVRDPServerChange(self):
640 | pass
641 |
642 | def onRemoteDisplayInfoChange(self):
643 | pass
644 |
645 | def onUSBControllerChange(self):
646 | pass
647 |
648 | def onUSBDeviceStateChange(self, device, attached, error):
649 | pass
650 |
651 | def onSharedFolderChange(self, scope):
652 | pass
653 |
654 | def onRuntimeError(self, fatal, id, message):
655 | pass
656 |
657 | def onCanShowWindow(self):
658 | return True
659 |
660 | def onShowWindow(self, winId):
661 | pass
662 |
--------------------------------------------------------------------------------