├── 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 | --------------------------------------------------------------------------------