Browse Source

core3: add different exceptions

From now on there are different exceptions which can be raise on
different occasions.

fixes QubesOS/qubes-issues#1279
Wojtek Porczyk 8 years ago
parent
commit
96efb4568a

+ 14 - 17
qubes/__init__.py

@@ -57,6 +57,8 @@ import lxml.etree
 
 
 import qubes.config
+import qubes.events
+import qubes.exc
 import qubes.ext
 
 
@@ -78,14 +80,6 @@ except ImportError:
     pass
 
 
-class QubesException(Exception):
-    '''Exception that can be shown to the user'''
-    pass
-
-
-import qubes.events
-
-
 class VMMConnection(object):
     '''Connection to Virtual Machine Manager (libvirt)'''
 
@@ -103,8 +97,8 @@ class VMMConnection(object):
     @offline_mode.setter
     def offline_mode(self, value):
         if value and self._libvirt_conn is not None:
-            raise QubesException(
-                "Cannot change offline mode while already connected")
+            raise qubes.exc.QubesException(
+                'Cannot change offline mode while already connected')
 
         self._offline_mode = value
 
@@ -120,7 +114,8 @@ class VMMConnection(object):
             return
         if self._offline_mode:
             # Do not initialize in offline mode
-            raise QubesException("VMM operations disabled in offline mode")
+            raise qubes.exc.QubesException(
+                'VMM operations disabled in offline mode')
 
         if 'xen.lowlevel.xs' in sys.modules:
             self._xs = xen.lowlevel.xs.xs()
@@ -128,7 +123,7 @@ class VMMConnection(object):
             self._xc = xen.lowlevel.xc.xc()
         self._libvirt_conn = libvirt.open(qubes.config.defaults['libvirt_uri'])
         if self._libvirt_conn is None:
-            raise QubesException("Failed connect to libvirt driver")
+            raise qubes.exc.QubesException('Failed connect to libvirt driver')
         libvirt.registerErrorHandler(self._libvirt_error_handler, None)
         atexit.register(self._libvirt_conn.close)
 
@@ -1371,7 +1366,7 @@ class Qubes(PropertyHolder):
         if isinstance(vm, qubes.vm.templatevm.TemplateVM):
             appvms = self.domains.get_vms_based_on(vm)
             if appvms:
-                raise QubesException(
+                raise qubes.exc.QubesException(
                     'Cannot remove template that has dependent AppVMs. '
                     'Affected are: {}'.format(', '.join(
                         vm.name for name in sorted(appvms))))
@@ -1399,8 +1394,9 @@ class Qubes(PropertyHolder):
             return
         if 'ntpd' in newvalue.services:
             if newvalue.services['ntpd']:
-                raise QubesException('Cannot set {!r} as {!r} property since '
-                    'it has ntpd enabled.'.format(newvalue, name))
+                raise qubes.exc.QubesVMError(newvalue,
+                    'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
+                        newvalue.name, name))
         else:
             newvalue.services['ntpd'] = False
 
@@ -1414,8 +1410,9 @@ class Qubes(PropertyHolder):
         if newvalue is not None and oldvalue is not None \
                 and oldvalue.is_running() and not newvalue.is_running() \
                 and self.domains.get_vms_connected_to(oldvalue):
-            raise QubesException('Cannot change default_netvm to domain that '
-                'is not running ({!r}).'.format(newvalue))
+            raise qubes.exc.QubesVMNotRunningError(newvalue,
+                'Cannot change {!r} to domain that '
+                'is not running ({!r}).'.format(name, newvalue.name))
 
 
     @qubes.events.handler('property-set:default_fw_netvm')

+ 147 - 0
qubes/exc.py

@@ -0,0 +1,147 @@
+#!/usr/bin/python2 -O
+# vim: fileencoding=utf-8
+
+#
+# The Qubes OS Project, https://www.qubes-os.org/
+#
+# Copyright (C) 2015  Joanna Rutkowska <joanna@invisiblethingslab.com>
+# Copyright (C) 2015  Wojtek Porczyk <woju@invisiblethingslab.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+'''
+Qubes OS exception hierarchy
+'''
+
+class QubesException(Exception):
+    '''Exception that can be shown to the user'''
+    pass
+
+
+class QubesVMNotFoundError(QubesException, KeyError):
+    '''Domain cannot be found in the system'''
+    def __init__(self, vmname):
+        super(QubesVMNotFoundError, self).__init__(
+            'No such domain: {!r}'.format(vmname))
+        self.vmname = vmname
+
+
+class QubesVMError(QubesException):
+    '''Some problem with domain state.'''
+    def __init__(self, vm, msg):
+        super(QubesVMError, self).__init__(msg)
+        self.vm = vm
+
+
+class QubesVMNotStartedError(QubesVMError):
+    '''Domain is not started.
+
+    This exception is thrown when machine is halted, but should be started
+    (that is, either running or paused).
+    '''
+    def __init__(self, vm, msg=None):
+        super(QubesVMNotStartedError, self).__init__(vm,
+            msg or 'Domain is powered off: {!r}'.format(vm.name))
+
+
+class QubesVMNotRunningError(QubesVMNotStartedError):
+    '''Domain is not running.
+
+    This exception is thrown when machine should be running but is either
+    halted or paused.
+    '''
+    def __init__(self, vm, msg=None):
+        super(QubesVMNotRunningError, self).__init__(vm,
+            msg or 'Domain not running (either powered off or paused): {!r}' \
+                .format(vm.name))
+
+
+class QubesVMNotPausedError(QubesVMNotStartedError):
+    '''Domain is not paused.
+
+    This exception is thrown when machine should be paused, but is not.
+    '''
+    def __init__(self, vm, msg=None):
+        super(QubesVMNotPausedError, self).__init__(vm,
+            msg or 'Domain is not paused: {!r}'.format(vm.name))
+
+
+class QubesVMNotSuspendedError(QubesVMError):
+    '''Domain is not suspended.
+
+    This exception is thrown when machine should be suspended but is either
+    halted or running.
+    '''
+    def __init__(self, vm, msg=None):
+        super(QubesVMNotSuspendedError, self).__init__(vm,
+            msg or 'Domain is not suspended: {!r}'.format(vm.name))
+
+
+class QubesVMNotHaltedError(QubesVMError):
+    '''Domain is not halted.
+
+    This exception is thrown when machine should be halted, but is not (either
+    running or paused).
+    '''
+    def __init__(self, vm, msg=None):
+        super(QubesVMNotHaltedError, self).__init__(vm,
+            msg or 'Domain is not powered off: {!r}'.format(vm.name))
+
+
+class QubesNoTemplateError(QubesVMError):
+    '''Cannot start domain, because there is no template'''
+    def __init__(self, vm, msg=None):
+        super(QubesNoTemplateError, self).__init__(
+            msg or 'Template for the domain {!r} not found'.format(vm.name))
+
+
+class QubesValueError(QubesException, ValueError):
+    '''Cannot set some value, because it is invalid, out of bounds, etc.'''
+    pass
+
+
+class QubesPropertyValueError(QubesValueError):
+    '''Cannot set value of qubes.property, because user-supplied value is wrong.
+    '''
+    def __init__(self, holder, prop, value, msg=None):
+        super(QubesPropertyValueError, self).__init__(
+            msg or 'Invalid value {!r} for property {!r} of {!r}'.format(
+                value, prop.__name__, holder))
+        self.holder = holder
+        self.prop = prop
+        self.value = value
+
+
+class QubesNotImplementedError(QubesException, NotImplementedError):
+    '''Thrown at user when some feature is not implemented'''
+    def __init__(self, msg=None):
+        super(QubesNotImplementedError, self).__init__(
+            msg or 'This feature is not available')
+
+
+class BackupCancelledError(QubesException):
+    '''Thrown at user when backup was manually cancelled'''
+    def __init__(self, msg=None):
+        super(BackupCancelledError, self).__init__(
+            msg or 'Backup cancelled')
+
+
+class QubesMemoryError(QubesException, MemoryError):
+    '''Cannot start domain, because not enough memory is available'''
+    def __init__(self, vm, msg=None):
+        super(QubesMemoryError, self).__init__(
+            msg or 'Not enough memory to start domain {!r}'.format(vm.name))
+        self.vm = vm

+ 5 - 4
qubes/storage/__init__.py

@@ -35,6 +35,7 @@ import subprocess
 import sys
 
 import qubes
+import qubes.exc
 import qubes.utils
 
 BLKSIZE = 512
@@ -214,22 +215,22 @@ class VMStorage(object):
 
     def verify_files(self):
         if not os.path.exists(self.vm.dir_path):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesVMError(self.vm,
                 'VM directory does not exist: {}'.format(self.vm.dir_path))
 
         if hasattr(self.vm, 'root_img') and not os.path.exists(self.root_img):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesVMError(self.vm,
                 'VM root image file does not exist: {}'.format(self.root_img))
 
         if hasattr(self.vm, 'private_img') \
                 and not os.path.exists(self.private_img):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesVMError(self.vm,
                 'VM private image file does not exist: {}'.format(
                     self.private_img))
 
         if self.modules_img is not None \
                 and not os.path.exists(self.modules_img):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesVMError(self.vm,
                 'VM kernel modules image does not exists: {}'.format(
                     self.modules_img))
 

+ 3 - 2
qubes/tests/vm/adminvm.py

@@ -25,6 +25,7 @@
 import unittest
 
 import qubes
+import qubes.exc
 import qubes.vm.adminvm
 
 import qubes.tests
@@ -98,10 +99,10 @@ class TC_00_AdminVM(qubes.tests.QubesTestCase):
         self.assertEqual(self.vm.get_private_img_sz(), 0)
 
     def test_310_start(self):
-        with self.assertRaises(qubes.QubesException):
+        with self.assertRaises(qubes.exc.QubesException):
             self.vm.start()
 
     @unittest.skip('this functionality is undecided')
     def test_311_suspend(self):
-        with self.assertRaises(qubes.QubesException):
+        with self.assertRaises(qubes.exc.QubesException):
             self.vm.suspend()

+ 3 - 2
qubes/tests/vm/qubesvm.py

@@ -26,6 +26,7 @@
 import unittest
 
 import qubes
+import qubes.exc
 import qubes.config
 import qubes.vm.qubesvm
 
@@ -89,13 +90,13 @@ class TC_00_setters(qubes.tests.QubesTestCase):
 
     def test_014_setter_name_running(self):
         self.vm.running = True
-        with self.assertRaises(qubes.QubesException):
+        with self.assertRaises(qubes.exc.QubesVMNotHaltedError):
             qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
 
     def test_015_setter_name_installed_by_rpm(self):
         # pylint: disable=invalid-name
         self.vm.installed_by_rpm = True
-        with self.assertRaises(qubes.QubesException):
+        with self.assertRaises(qubes.exc.QubesException):
             qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
 
 

+ 2 - 2
qubes/tools/qvm_kill.py

@@ -26,7 +26,7 @@
 
 
 import sys
-import qubes
+import qubes.exc
 import qubes.tools
 
 parser = qubes.tools.QubesArgumentParser(
@@ -45,7 +45,7 @@ def main(args=None):
 
     try:
         args.vm.force_shutdown()
-    except (IOError, OSError, qubes.QubesException) as e:
+    except (IOError, OSError, qubes.exc.QubesException) as e:
         parser.error_runtime(str(e))
 
     return True

+ 1 - 5
qubes/tools/qvm_start.py

@@ -131,11 +131,7 @@ def main(args=None):
             preparing_dvm=args.preparing_dvm,
             start_guid=args.start_guid)
             # notify_function=
-    except MemoryError:
-        # TODO tray
-        parser.error_runtime(
-            'not enough memory to start domain {!r}'.format(vm.name))
-    except qubes.QubesException as e:
+    except qubes.exc.QubesException as e:
         parser.error_runtime('Qubes error: {!r}'.format(e))
 
     return True

+ 2 - 2
qubes/utils.py

@@ -32,7 +32,7 @@ import docutils
 import docutils.core
 import docutils.io
 
-import qubes
+import qubes.exc
 
 
 def get_timezone():
@@ -103,4 +103,4 @@ def parse_size(size):
             size = size[:-len(unit)].strip()
             return int(size)*multiplier
 
-    raise qubes.QubesException("Invalid size: {0}.".format(size))
+    raise qubes.exc.QubesException("Invalid size: {0}.".format(size))

+ 1 - 2
qubes/vm/__init__.py

@@ -291,8 +291,7 @@ class BaseVM(qubes.PropertyHolder):
 
         dev_match = re.match(r'([0-9a-f]+):([0-9a-f]+)\.([0-9a-f]+)', address)
         if not dev_match:
-            raise qubes.QubesException(
-                'Invalid PCI device address: {}'.format(address))
+            raise ValueError('Invalid PCI device address: {!r}'.format(address))
 
         hostdev = lxml.etree.Element('hostdev', type='pci', managed='yes')
         source = lxml.etree.Element('source')

+ 2 - 1
qubes/vm/adminvm.py

@@ -25,6 +25,7 @@
 #
 
 import qubes
+import qubes.exc
 import qubes.vm.qubesvm
 
 class AdminVM(qubes.vm.qubesvm.QubesVM):
@@ -150,7 +151,7 @@ class AdminVM(qubes.vm.qubesvm.QubesVM):
         .. seealso:
            :py:meth:`qubes.vm.qubesvm.QubesVM.start`
         ''' # pylint: disable=unused-argument
-        raise qubes.QubesException('Cannot start Dom0 fake domain!')
+        raise qubes.exc.QubesVMError('Cannot start Dom0 fake domain!')
 
 
     def suspend(self):

+ 67 - 64
qubes/vm/qubesvm.py

@@ -42,8 +42,7 @@ import libvirt
 
 import qubes
 import qubes.config
-#import qubes.qmemman
-#import qubes.qmemman_algo
+import qubes.exc
 import qubes.storage
 import qubes.utils
 import qubes.vm
@@ -78,12 +77,13 @@ def _setter_name(self, prop, value):
         raise ValueError('{} value contains illegal characters'.format(
             prop.__name__))
     if self.is_running():
-        raise qubes.QubesException('Cannot change name of running VM')
+        raise qubes.exc.QubesVMNotHaltedError(
+            self, 'Cannot change name of running VM')
 
     try:
         if self.installed_by_rpm:
-            raise qubes.QubesException('Cannot rename VM installed by RPM -- '
-                'first clone VM and then use yum to remove package.')
+            raise qubes.exc.QubesException('Cannot rename VM installed by RPM '
+                '-- first clone VM and then use yum to remove package.')
     except AttributeError:
         pass
 
@@ -97,10 +97,11 @@ def _setter_kernel(self, prop, value):
         qubes.config.system_path['qubes_kernels_base_dir'],
         value)
     if not os.path.exists(dirname):
-        raise qubes.QubesException('Kernel {!r} not installed'.format(value))
+        raise qubes.exc.QubesPropertyValueError(self, prop, value,
+            'Kernel {!r} not installed'.format(value))
     for filename in ('vmlinuz', 'modules.img'):
         if not os.path.exists(os.path.join(dirname, filename)):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesPropertyValueError(
                 'Kernel {!r} not properly installed: missing {!r} file'.format(
                     value, filename))
     return value
@@ -557,8 +558,9 @@ class QubesVM(qubes.vm.BaseVM):
         # pylint: disable=unused-argument
         if self.is_running() and new_netvm is not None \
                 and not new_netvm.is_running():
-            raise qubes.QubesException(
-                'Cannot dynamically attach to stopped NetVM')
+            raise qubes.exc.QubesVMNotStartedError(new_netvm,
+                'Cannot dynamically attach to stopped NetVM: {!r}'.format(
+                    new_netvm))
 
         if self.netvm is not None:
             del self.netvm.connected_vms[self]
@@ -601,7 +603,8 @@ class QubesVM(qubes.vm.BaseVM):
         # pylint: disable=unused-argument
         # TODO not self.is_stopped() would be more appropriate
         if self.is_running():
-            raise qubes.QubesException('Cannot change name of running domain')
+            raise qubes.exc.QubesVMNotHaltedError(
+                'Cannot change name of running domain {!r}'.format(oldvalue))
 
 
     @qubes.events.handler('property-set:name')
@@ -640,7 +643,7 @@ class QubesVM(qubes.vm.BaseVM):
         if subprocess.call(['sudo', 'systemctl',
                 ('enable' if value else 'disable'),
                 'qubes-vm@{}.service'.format(self.name)]):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesException(
                 'Failed to set autostart for VM via systemctl')
 
 
@@ -648,7 +651,7 @@ class QubesVM(qubes.vm.BaseVM):
     def on_device_pre_attached_pci(self, event, pci):
         # pylint: disable=unused-argument
         if not os.path.exists('/sys/bus/pci/devices/0000:{}'.format(pci)):
-            raise qubes.QubesException('Invalid PCI device: {}'.format(pci))
+            raise qubes.exc.QubesException('Invalid PCI device: {}'.format(pci))
 
         if not self.is_running():
             return
@@ -707,7 +710,7 @@ class QubesVM(qubes.vm.BaseVM):
         # Intentionally not used is_running(): eliminate also "Paused",
         # "Crashed", "Halting"
         if self.get_power_state() != 'Halted':
-            raise qubes.QubesException('VM is already running!')
+            raise qubes.exc.QubesVMNotHaltedError(self)
 
         self.log.info('Starting {}'.format(self.name))
 
@@ -732,8 +735,7 @@ class QubesVM(qubes.vm.BaseVM):
                 raise IOError('Failed to connect to qmemman: {!s}'.format(e))
             if not got_memory:
                 qmemman_client.close()
-                raise MemoryError(
-                    'Insufficient memory to start VM {!r}'.format(self.name))
+                raise qubes.exc.QubesMemoryError(self)
 
         # Bind pci devices to pciback driver
         for pci in self.devices['pci']:
@@ -794,11 +796,12 @@ class QubesVM(qubes.vm.BaseVM):
     def shutdown(self):
         '''Shutdown domain.
 
-        :raises QubesException: when domain is already shut down.
+        :raises qubes.exc.QubesVMNotStartedError: \
+            when domain is already shut down.
         '''
 
-        if not self.is_running():
-            raise qubes.QubesException("VM already stopped!")
+        if not self.is_running(): # TODO not self.is_halted()
+            raise qubes.exc.QubesVMNotStartedError(self)
 
         self.libvirt_domain.shutdown()
 
@@ -806,11 +809,12 @@ class QubesVM(qubes.vm.BaseVM):
     def force_shutdown(self):
         '''Forcefuly shutdown (destroy) domain.
 
-        :raises QubesException: when domain is already shut down.
+        :raises qubes.exc.QubesVMNotStartedError: \
+            when domain is already shut down.
         '''
 
         if not self.is_running() and not self.is_paused():
-            raise qubes.QubesException('VM already stopped!')
+            raise qubes.exc.QubesVMNotStartedError(self)
 
         self.libvirt_domain.destroy()
 
@@ -818,15 +822,19 @@ class QubesVM(qubes.vm.BaseVM):
     def suspend(self):
         '''Suspend (pause) domain.
 
-        :raises qubes.QubesException: when domain is already shut down.
-        :raises NotImplemetedError: when domain has PCI devices attached.
+        :raises qubes.exc.QubesVMNotRunnignError: \
+            when domain is already shut down.
+        :raises qubes.exc.QubesNotImplemetedError: \
+            when domain has PCI devices attached.
         '''
 
         if not self.is_running() and not self.is_paused():
-            raise qubes.QubesException('VM already stopped!')
+            raise qubes.exc.QubesVMNotRunningError(self)
 
         if len(self.devices['pci']) > 0:
-            raise NotImplementedError()
+            raise qubes.exc.QubesNotImplementedError(
+                'Cannot suspend domain {!r} which has PCI devices attached' \
+                    .format(self.name))
         else:
             self.libvirt_domain.suspend()
 
@@ -836,7 +844,7 @@ class QubesVM(qubes.vm.BaseVM):
         :py:meth:`suspend`.'''
 
         if not self.is_running():
-            raise qubes.QubesException('VM not running!')
+            raise qubes.exc.QubesVMNotRunningError(self)
 
         self.suspend()
 
@@ -844,18 +852,21 @@ class QubesVM(qubes.vm.BaseVM):
     def resume(self):
         '''Resume suspended domain.
 
-        :raises NotImplemetedError: when machine is alread suspended.
+        :raises qubes.exc.QubesVMNotSuspendedError: when machine is not paused
+        :raises qubes.exc.QubesVMError: when machine is suspended
         '''
 
         if self.get_power_state() == "Suspended":
-            raise NotImplementedError()
+            raise qubes.exc.QubesVMError(self,
+                'Cannot resume suspended domain {!r}'.format(self.name))
         else:
             self.unpause()
 
+
     def unpause(self):
         '''Resume (unpause) a domain'''
         if not self.is_paused():
-            raise qubes.QubesException('VM not paused!')
+            raise qubes.exc.QubesVMNotPausedError(self)
 
         self.libvirt_domain.resume()
 
@@ -891,28 +902,21 @@ class QubesVM(qubes.vm.BaseVM):
         null = None
         if not self.is_running() and not self.is_paused():
             if not autostart:
-                raise qubes.QubesException('VM not running')
+                raise qubes.exc.QubesVMNotRunningError(self)
 
-            try:
-                if notify_function is not None:
-                    notify_function('info',
-                        'Starting the {!r} VM...'.format(self.name))
-                self.start(start_guid=gui, notify_function=notify_function)
-
-            except (IOError, OSError, qubes.QubesException) as e:
-                raise qubes.QubesException(
-                    'Error while starting the {!r} VM: {!s}'.format(
-                        self.name, e))
-            except MemoryError:
-                raise qubes.QubesException('Not enough memory to start {!r} VM!'
-                    ' Close one or more running VMs and try again.'.format(
-                        self.name))
+            if notify_function is not None:
+                notify_function('info',
+                    'Starting the {!r} VM...'.format(self.name))
+            self.start(start_guid=gui, notify_function=notify_function)
 
         if self.is_paused():
-            raise qubes.QubesException('VM is paused')
+            # XXX what about autostart?
+            raise qubes.exc.QubesVMNotRunningError(
+                self, 'Domain {!r} is paused'.format(self.name))
+
         if not self.is_qrexec_running():
-            raise qubes.QubesException(
-                "Domain '{}': qrexec not connected.".format(self.name))
+            raise qubes.exc.QubesVMError(
+                self, 'Domain {!r}: qrexec not connected'.format(self.name))
 
         if gui and os.getenv("DISPLAY") is not None \
                 and not self.is_guid_running():
@@ -1018,7 +1022,8 @@ class QubesVM(qubes.vm.BaseVM):
 
         retcode = subprocess.call(guid_cmd)
         if retcode != 0:
-            raise qubes.QubesException('Cannot start qubes-guid!')
+            raise qubes.exc.QubesVMError(self,
+                'Cannot start qubes-guid for domain {!r}'.format(self.name))
 
         self.log.info('Sending monitor layout')
 
@@ -1123,8 +1128,8 @@ class QubesVM(qubes.vm.BaseVM):
     def resize_private_img(self, size):
         '''Resize private image.'''
 
-        # TODO QubesValueError, not assert
-        assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
+        if size >= self.get_private_img_sz():
+            raise qubes.exc.QubesValueError('Cannot shrink private.img')
 
         # resize the image
         self.storage.resize_private_img(size)
@@ -1139,8 +1144,9 @@ class QubesVM(qubes.vm.BaseVM):
                     sleep 0.2;
                 done;
                 resize2fs /dev/xvdb'''.format(size), user="root", wait=True)
+
         if retcode != 0:
-            raise qubes.QubesException('resize2fs failed')
+            raise qubes.exc.QubesException('resize2fs failed')
 
 
     def remove_from_disk(self):
@@ -1155,8 +1161,9 @@ class QubesVM(qubes.vm.BaseVM):
         :param qubes.vm.qubesvm.QubesVM src: source VM
         '''
 
-        if src.is_running():
-            raise qubes.QubesException('Attempt to clone a running VM!')
+        if src.is_running(): # XXX what about paused?
+            raise qubes.exc.QubesVMNotHaltedError(
+                self, 'Cannot clone a running domain {!r}'.format(self.name))
 
         self.storage.clone_disk_files(src, verbose=False)
 
@@ -1184,10 +1191,8 @@ class QubesVM(qubes.vm.BaseVM):
         '''Attach network in this machine to it's netvm.'''
 
         if not self.is_running():
-            raise qubes.QubesException('VM not running!')
-
-        if self.netvm is None:
-            raise qubes.QubesException('NetVM not set!')
+            raise qubes.exc.QubesVMNotRunningError(self)
+        assert self.netvm is not None
 
         if not self.netvm.is_running():
             self.log.info('Starting NetVM ({0})'.format(self.netvm.name))
@@ -1201,11 +1206,8 @@ class QubesVM(qubes.vm.BaseVM):
         '''Detach machine from it's netvm'''
 
         if not self.is_running():
-            raise qubes.QubesException('VM not running!')
-
-        if self.netvm is None:
-            raise qubes.QubesException('NetVM not set!')
-
+            raise qubes.exc.QubesVMNotRunningError(self)
+        assert self.netvm is not None
 
         self.libvirt_domain.detachDevice(lxml.etree.ElementTree(
             self.lvxml_net_dev(self.ip, self.mac, self.netvm)).tostring())
@@ -1548,12 +1550,13 @@ class QubesVM(qubes.vm.BaseVM):
 
         if not os.path.exists(
                 os.path.join(self.storage.kernels_dir, 'vmlinuz')):
-            raise qubes.QubesException('VM kernel does not exist: {0}'.format(
-                os.path.join(self.storage.kernels_dir, 'vmlinuz')))
+            raise qubes.exc.QubesException(
+                'VM kernel does not exist: {0}'.format(
+                    os.path.join(self.storage.kernels_dir, 'vmlinuz')))
 
         if not os.path.exists(
                 os.path.join(self.storage.kernels_dir, 'initramfs')):
-            raise qubes.QubesException(
+            raise qubes.exc.QubesException(
                 'VM initramfs does not exist: {0}'.format(
                     os.path.join(self.storage.kernels_dir, 'initramfs')))
 

+ 1 - 0
rpm_spec/core-dom0.spec

@@ -199,6 +199,7 @@ fi
 %{python_sitelib}/qubes/config.py*
 %{python_sitelib}/qubes/dochelpers.py*
 %{python_sitelib}/qubes/events.py*
+%{python_sitelib}/qubes/exc.py*
 %{python_sitelib}/qubes/log.py*
 %{python_sitelib}/qubes/plugins.py*
 %{python_sitelib}/qubes/rngdoc.py*