Handling VM dependencies in GUI tools
Added the following reactions ot VM dependencies in GUI tools:
- Qube Manager will inform the user why they cannot delete a VM (which
properties of which VMS [or global] are using the VM)
- Settings window will try to intelligently rename VMs (change
properties to the new name, if possible, and inform the user what went
wrong if not)
- Settings window will inform the user why they cannot delete a VM
Also, renaming VM from Settings launched from Qube Manager will
refresh the VM list through a horrible hack, to be replaced by a neater
Admin API solution in some near future.
depends on ca848ca7bd
fixes QubesOS/qubes-issues#3992
fixes QubesOS/qubes-issues#3993
fixes QubesOS/qubes-issues#3892
fixes QubesOS/qubes-issues#3499
			
			
This commit is contained in:
		
							parent
							
								
									d46bf2aa8e
								
							
						
					
					
						commit
						b2fdbb950a
					
				@ -50,6 +50,7 @@ from . import global_settings
 | 
				
			|||||||
from . import restore
 | 
					from . import restore
 | 
				
			||||||
from . import backup
 | 
					from . import backup
 | 
				
			||||||
from . import log_dialog
 | 
					from . import log_dialog
 | 
				
			||||||
 | 
					from . import utils as manager_utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SearchBox(QtGui.QLineEdit):
 | 
					class SearchBox(QtGui.QLineEdit):
 | 
				
			||||||
@ -413,7 +414,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
 | 
				
			|||||||
    def check_updates(self):
 | 
					    def check_updates(self):
 | 
				
			||||||
        for vm in self.qubes_app.domains:
 | 
					        for vm in self.qubes_app.domains:
 | 
				
			||||||
            if vm.klass in {'TemplateVM', 'StandaloneVM'}:
 | 
					            if vm.klass in {'TemplateVM', 'StandaloneVM'}:
 | 
				
			||||||
                self.vms_in_table[vm.qid].update()
 | 
					                try:
 | 
				
			||||||
 | 
					                    self.vms_in_table[vm.qid].update()
 | 
				
			||||||
 | 
					                except exc.QubesException:
 | 
				
			||||||
 | 
					                    # the VM might have vanished in the meantime
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_domain_added(self, _, domain):
 | 
					    def on_domain_added(self, _, domain):
 | 
				
			||||||
        #needs to clear cache
 | 
					        #needs to clear cache
 | 
				
			||||||
@ -686,26 +691,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        vm = self.get_selected_vm()
 | 
					        vm = self.get_selected_vm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dependencies = utils.vm_usage(self.qubes_app, vm)
 | 
					        dependencies = utils.vm_dependencies(self.qubes_app, vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if dependencies:
 | 
					        if dependencies:
 | 
				
			||||||
            list_text = "<br>"
 | 
					            list_text = "<br>" + \
 | 
				
			||||||
            for (holder, prop) in dependencies:
 | 
					                        manager_utils.format_dependencies_list(dependencies) + \
 | 
				
			||||||
                if holder is None:
 | 
					                        "<br>"
 | 
				
			||||||
                    list_text += "- Global property <b>{}</b> <br>".format(prop)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    list_text += "- <b>{}</b> for qube <b>{}</b> <br>".format(
 | 
					 | 
				
			||||||
                        prop, holder.name)
 | 
					 | 
				
			||||||
            list_text += "<br>"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            info_dialog = QtGui.QMessageBox(self)
 | 
					            info_dialog = QtGui.QMessageBox(self)
 | 
				
			||||||
            info_dialog.setWindowTitle(self.tr("Warning!"))
 | 
					            info_dialog.setWindowTitle(self.tr("Warning!"))
 | 
				
			||||||
            info_dialog.setText(
 | 
					            info_dialog.setText(
 | 
				
			||||||
                self.tr("This qube cannot be removed. It is used as:"
 | 
					                self.tr("This qube cannot be removed. It is used as:"
 | 
				
			||||||
                        " <br>" + list_text + "<small>If you want to "
 | 
					                        " <br> {} <small>If you want to  remove this qube, "
 | 
				
			||||||
                        " remove this qube, you should remove or change "
 | 
					                        "you should remove or change settings of each qube "
 | 
				
			||||||
                        " settings of each qube or setting that uses"
 | 
					                        "or setting that uses it.</small>".format(list_text)))
 | 
				
			||||||
                                              " it.</small>"))
 | 
					 | 
				
			||||||
            info_dialog.setModal(False)
 | 
					            info_dialog.setModal(False)
 | 
				
			||||||
            info_dialog.show()
 | 
					            info_dialog.show()
 | 
				
			||||||
            self.qt_app.processEvents()
 | 
					            self.qt_app.processEvents()
 | 
				
			||||||
@ -1001,8 +1000,24 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
 | 
				
			|||||||
            settings_window = settings.VMSettingsWindow(
 | 
					            settings_window = settings.VMSettingsWindow(
 | 
				
			||||||
                vm, self.qt_app, "basic")
 | 
					                vm, self.qt_app, "basic")
 | 
				
			||||||
            settings_window.exec_()
 | 
					            settings_window.exec_()
 | 
				
			||||||
            self.vms_in_table[vm.qid].update()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            vm_deleted = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                # the VM might not exist after running Settings - it might
 | 
				
			||||||
 | 
					                # have been cloned or removed
 | 
				
			||||||
 | 
					                self.vms_in_table[vm.qid].update()
 | 
				
			||||||
 | 
					            except exc.QubesException:
 | 
				
			||||||
 | 
					                # TODO: this will be replaced by proper signal handling once
 | 
				
			||||||
 | 
					                # settings are migrated to AdminAPI
 | 
				
			||||||
 | 
					                vm_deleted = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if vm_deleted:
 | 
				
			||||||
 | 
					                for row in self.vms_in_table:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        self.vms_in_table[row].update()
 | 
				
			||||||
 | 
					                    except exc.QubesException:
 | 
				
			||||||
 | 
					                        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # noinspection PyArgumentList
 | 
					    # noinspection PyArgumentList
 | 
				
			||||||
    @QtCore.pyqtSlot(name='on_action_appmenus_triggered')
 | 
					    @QtCore.pyqtSlot(name='on_action_appmenus_triggered')
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,7 @@ import traceback
 | 
				
			|||||||
import sys
 | 
					import sys
 | 
				
			||||||
from qubesadmin.tools import QubesArgumentParser
 | 
					from qubesadmin.tools import QubesArgumentParser
 | 
				
			||||||
from qubesadmin import devices
 | 
					from qubesadmin import devices
 | 
				
			||||||
 | 
					from qubesadmin import utils as admin_utils
 | 
				
			||||||
import qubesadmin.exc
 | 
					import qubesadmin.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import utils
 | 
					from . import utils
 | 
				
			||||||
@ -481,10 +482,35 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _rename_vm(self, t_monitor, name):
 | 
					    def _rename_vm(self, t_monitor, name, dependencies):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.vm.app.clone_vm(self.vm, name)
 | 
					            new_vm = self.vm.app.clone_vm(self.vm, name)
 | 
				
			||||||
            del self.vm.app.domains[self.vm.name]
 | 
					
 | 
				
			||||||
 | 
					            failed_props = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (holder, prop) in dependencies:
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    if holder is None:
 | 
				
			||||||
 | 
					                        setattr(self.vm.app, prop, new_vm)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        setattr(holder, prop, new_vm)
 | 
				
			||||||
 | 
					                except qubesadmin.exc.QubesException as qex:
 | 
				
			||||||
 | 
					                    failed_props += (holder, prop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not failed_props:
 | 
				
			||||||
 | 
					                del self.vm.app.domains[self.vm.name]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                list_text = utils.format_dependencies_list(failed_props)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                QtGui.QMessageBox.warning(
 | 
				
			||||||
 | 
					                    self,
 | 
				
			||||||
 | 
					                    self.tr("Warning: rename partially unsuccessful"),
 | 
				
			||||||
 | 
					                    self.tr("Some properties could not be changed to the new "
 | 
				
			||||||
 | 
					                            "name. The system has now both {} and {} qubes. "
 | 
				
			||||||
 | 
					                            "To resolve this, please check and change the "
 | 
				
			||||||
 | 
					                            "following properties and remove the qube {} "
 | 
				
			||||||
 | 
					                            "manually.<br> ".format(
 | 
				
			||||||
 | 
					                                self.vm.name, name, self.vm.name) + list_text))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except qubesadmin.exc.QubesException as qex:
 | 
					        except qubesadmin.exc.QubesException as qex:
 | 
				
			||||||
            t_monitor.set_error_msg(str(qex))
 | 
					            t_monitor.set_error_msg(str(qex))
 | 
				
			||||||
@ -494,13 +520,31 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
 | 
				
			|||||||
        t_monitor.set_finished()
 | 
					        t_monitor.set_finished()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rename_vm(self):
 | 
					    def rename_vm(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        running_dependencies = [vm.name for (vm, prop) in dependencies
 | 
				
			||||||
 | 
					                                if vm and prop == 'template'
 | 
				
			||||||
 | 
					                                and vm.is_running()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if running_dependencies:
 | 
				
			||||||
 | 
					            QtGui.QMessageBox.warning(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                self.tr("Qube cannot be renamed!"),
 | 
				
			||||||
 | 
					                self.tr(
 | 
				
			||||||
 | 
					                    "The following qubes using this qube as a template are "
 | 
				
			||||||
 | 
					                    "running: <br> {}. <br> In order to rename this qube, you "
 | 
				
			||||||
 | 
					                    "must first shut them down.".format(
 | 
				
			||||||
 | 
					                        ", ".join(running_dependencies))))
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new_vm_name, ok = QtGui.QInputDialog.getText(
 | 
					        new_vm_name, ok = QtGui.QInputDialog.getText(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            self.tr('Rename qube'),
 | 
					            self.tr('Rename qube'),
 | 
				
			||||||
            self.tr('New name: (WARNING: all other changes will be discarded)'))
 | 
					            self.tr('New name: (WARNING: all other changes will be discarded)'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ok:
 | 
					        if ok:
 | 
				
			||||||
            if self._run_in_thread(self._rename_vm, new_vm_name):
 | 
					            if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
 | 
				
			||||||
                self.done(0)
 | 
					                self.done(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _remove_vm(self, t_monitor):
 | 
					    def _remove_vm(self, t_monitor):
 | 
				
			||||||
@ -516,6 +560,21 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def remove_vm(self):
 | 
					    def remove_vm(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if dependencies:
 | 
				
			||||||
 | 
					            list_text = utils.format_dependencies_list(dependencies)
 | 
				
			||||||
 | 
					            QtGui.QMessageBox.warning(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                self.tr("Qube cannot be removed!"),
 | 
				
			||||||
 | 
					                self.tr("This qube cannot be removed. It is used as:"
 | 
				
			||||||
 | 
					                        " <br> {} <small>If you want to  remove this qube, "
 | 
				
			||||||
 | 
					                        "you should remove or change settings of each qube "
 | 
				
			||||||
 | 
					                        "or setting that uses it.</small>".format(list_text)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        answer, ok = QtGui.QInputDialog.getText(
 | 
					        answer, ok = QtGui.QInputDialog.getText(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            self.tr('Delete qube'),
 | 
					            self.tr('Delete qube'),
 | 
				
			||||||
 | 
				
			|||||||
@ -172,3 +172,18 @@ def get_path_from_vm(vm, service_name):
 | 
				
			|||||||
        assert '\0' not in untrusted_path
 | 
					        assert '\0' not in untrusted_path
 | 
				
			||||||
        return untrusted_path.strip()
 | 
					        return untrusted_path.strip()
 | 
				
			||||||
    raise ValueError('Unexpected characters in path.')
 | 
					    raise ValueError('Unexpected characters in path.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def format_dependencies_list(dependencies):
 | 
				
			||||||
 | 
					    """Given a list of tuples representing properties, formats them in
 | 
				
			||||||
 | 
					    a readable list."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_text = ""
 | 
				
			||||||
 | 
					    for (holder, prop) in dependencies:
 | 
				
			||||||
 | 
					        if holder is None:
 | 
				
			||||||
 | 
					            list_text += "- Global property <b>{}</b> <br>".format(prop)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            list_text += "- <b>{}</b> for qube <b>{}</b> <br>".format(
 | 
				
			||||||
 | 
					                prop, holder.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return list_text
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user