Merge branch 'master' of https://github.com/QubesOS/qubes-manager into cascade
This commit is contained in:
commit
d86c254031
44
debian/changelog
vendored
44
debian/changelog
vendored
@ -1,3 +1,47 @@
|
|||||||
|
qubes-manager (4.1.15-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Marek Marczykowski-Górecki ]
|
||||||
|
* create_new_vm: enable/disable "install system" option on template
|
||||||
|
change
|
||||||
|
* tests: update for recent changes
|
||||||
|
|
||||||
|
[ Marta Marczykowska-Górecka ]
|
||||||
|
* Enable word wrap for kernel opts in VM settings
|
||||||
|
|
||||||
|
[ donoban ]
|
||||||
|
* Just warning message improve
|
||||||
|
* Added template_menu
|
||||||
|
* Added properly handling when templates are added and removed
|
||||||
|
* added network_menu
|
||||||
|
* Added 'None' netvm option
|
||||||
|
* Added try/except for change_network
|
||||||
|
* Added network_menu updates
|
||||||
|
* Added QMessageBox if netvm is halted and user wants to start it
|
||||||
|
* Moved change_* funcs after __init__()
|
||||||
|
* Added Template Change Confirmation
|
||||||
|
* Added change network confirmation
|
||||||
|
* Changed checkboxes to icons
|
||||||
|
* Added proper error handling and Check netvm_name is not None
|
||||||
|
* Added error message to dialogs
|
||||||
|
* Added try/except for starting netvm
|
||||||
|
* Better dialog creation
|
||||||
|
* Added wait argument to start_vm
|
||||||
|
* Wrap warnings message in self.tr()
|
||||||
|
* Added default option for network change
|
||||||
|
* Fix possible 'None' default error
|
||||||
|
* Display default netvm
|
||||||
|
* Disable network menu for templates
|
||||||
|
* Add warning if trying to change template VM
|
||||||
|
* Fix too long line
|
||||||
|
* Fix coherence in network menu when adding/removing domains
|
||||||
|
|
||||||
|
[ Marek Marczykowski-Górecki ]
|
||||||
|
* Restore checkboxes to show/hide columns
|
||||||
|
* tests: add a decorator for keeping event listener alive
|
||||||
|
* tests: changing netvm and template via right click
|
||||||
|
|
||||||
|
-- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com> Thu, 25 Feb 2021 17:47:25 +0100
|
||||||
|
|
||||||
qubes-manager (4.1.14-1) unstable; urgency=medium
|
qubes-manager (4.1.14-1) unstable; urgency=medium
|
||||||
|
|
||||||
[ Frédéric Pierret (fepitre) ]
|
[ Frédéric Pierret (fepitre) ]
|
||||||
|
@ -168,6 +168,8 @@ class NewVmDlg(QtWidgets.QDialog, Ui_NewVMDlg):
|
|||||||
|
|
||||||
self.vm_type.currentIndexChanged.connect(self.type_change)
|
self.vm_type.currentIndexChanged.connect(self.type_change)
|
||||||
|
|
||||||
|
self.template_vm.currentIndexChanged.connect(self.template_change)
|
||||||
|
|
||||||
self.launch_settings.stateChanged.connect(self.settings_change)
|
self.launch_settings.stateChanged.connect(self.settings_change)
|
||||||
self.install_system.stateChanged.connect(self.install_change)
|
self.install_system.stateChanged.connect(self.install_change)
|
||||||
|
|
||||||
@ -287,6 +289,17 @@ class NewVmDlg(QtWidgets.QDialog, Ui_NewVMDlg):
|
|||||||
self.template_vm.setCurrentIndex(0)
|
self.template_vm.setCurrentIndex(0)
|
||||||
self.template_type = "template"
|
self.template_type = "template"
|
||||||
|
|
||||||
|
def template_change(self):
|
||||||
|
template = self.template_vm.currentData()
|
||||||
|
klass = self.vm_type.currentData()
|
||||||
|
|
||||||
|
if klass in ['TemplateVM', 'StandaloneVM'] and template is None:
|
||||||
|
self.install_system.setEnabled(True)
|
||||||
|
self.install_system.setChecked(True)
|
||||||
|
else:
|
||||||
|
self.install_system.setEnabled(False)
|
||||||
|
self.install_system.setChecked(False)
|
||||||
|
|
||||||
def install_change(self):
|
def install_change(self):
|
||||||
if self.install_system.isChecked():
|
if self.install_system.isChecked():
|
||||||
self.launch_settings.setChecked(False)
|
self.launch_settings.setChecked(False)
|
||||||
|
@ -707,6 +707,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
self.frame_width = 0
|
self.frame_width = 0
|
||||||
self.frame_height = 0
|
self.frame_height = 0
|
||||||
|
|
||||||
|
self.init_template_menu()
|
||||||
|
self.init_network_menu()
|
||||||
self.__init_context_menu()
|
self.__init_context_menu()
|
||||||
|
|
||||||
self.tools_context_menu = QMenu(self)
|
self.tools_context_menu = QMenu(self)
|
||||||
@ -738,6 +740,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
self.proxy.setFilterKeyColumn(2)
|
self.proxy.setFilterKeyColumn(2)
|
||||||
self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
|
self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
|
||||||
self.proxy.layoutChanged.connect(self.save_sorting)
|
self.proxy.layoutChanged.connect(self.save_sorting)
|
||||||
|
self.proxy.layoutChanged.connect(self.update_template_menu)
|
||||||
|
self.proxy.layoutChanged.connect(self.update_network_menu)
|
||||||
|
|
||||||
self.show_running.stateChanged.connect(self.invalidate)
|
self.show_running.stateChanged.connect(self.invalidate)
|
||||||
self.show_halted.stateChanged.connect(self.invalidate)
|
self.show_halted.stateChanged.connect(self.invalidate)
|
||||||
@ -820,9 +824,77 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
|
|
||||||
self.check_updates()
|
self.check_updates()
|
||||||
|
|
||||||
|
def change_template(self, template):
|
||||||
|
selected_vms = self.get_selected_vms()
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self, self.tr("Template Change Confirmation"),
|
||||||
|
self.tr("Do you want to change '{0}'<br>"
|
||||||
|
"to Template <b>'{1}'</b>?").format(
|
||||||
|
', '.join(vm.name for vm in selected_vms), template),
|
||||||
|
QMessageBox.Yes | QMessageBox.Cancel)
|
||||||
|
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
errors = []
|
||||||
|
for info in selected_vms:
|
||||||
|
try:
|
||||||
|
info.vm.template = template
|
||||||
|
except exc.QubesValueError as ex:
|
||||||
|
errors.append((info.name, str(ex)))
|
||||||
|
|
||||||
|
for error in errors:
|
||||||
|
QMessageBox.warning(self, self.tr("{0} template change failed!")
|
||||||
|
.format(error[0]), error[1])
|
||||||
|
|
||||||
|
|
||||||
|
def change_network(self, netvm_name):
|
||||||
|
selected_vms = self.get_selected_vms()
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self, self.tr("Network Change Confirmation"),
|
||||||
|
self.tr("Do you want to change '{0}'<br>"
|
||||||
|
"to Network <b>'{1}'</b>?").format(
|
||||||
|
', '.join(vm.name for vm in selected_vms), netvm_name),
|
||||||
|
QMessageBox.Yes | QMessageBox.Cancel)
|
||||||
|
|
||||||
|
if reply != QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
if netvm_name not in [None, 'default']:
|
||||||
|
check_power = any(info.state['power'] == 'Running' for info
|
||||||
|
in self.get_selected_vms())
|
||||||
|
netvm = self.qubes_cache.get_vm(name=netvm_name)
|
||||||
|
if check_power and netvm.state['power'] != 'Running':
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self, self.tr("Qube Start Confirmation"),
|
||||||
|
self.tr("<br>Can not change netvm to a halted Qube.<br>"
|
||||||
|
"Do you want to start the Qube <b>'{0}'</b>?").format(
|
||||||
|
netvm_name),
|
||||||
|
QMessageBox.Yes | QMessageBox.Cancel)
|
||||||
|
|
||||||
|
if reply == QMessageBox.Yes:
|
||||||
|
self.start_vm(netvm.vm, True)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for info in self.get_selected_vms():
|
||||||
|
try:
|
||||||
|
if netvm_name == 'default':
|
||||||
|
delattr(info.vm, 'netvm')
|
||||||
|
else:
|
||||||
|
info.vm.netvm = netvm_name
|
||||||
|
except exc.QubesValueError as ex:
|
||||||
|
errors.append((info.name, str(ex)))
|
||||||
|
|
||||||
|
for error in errors:
|
||||||
|
QMessageBox.warning(self, self.tr("{0} network change failed!")
|
||||||
|
.format(error[0]), error[1])
|
||||||
|
|
||||||
|
|
||||||
def __init_context_menu(self):
|
def __init_context_menu(self):
|
||||||
self.context_menu = QMenu(self)
|
self.context_menu = QMenu(self)
|
||||||
self.context_menu.addAction(self.action_settings)
|
self.context_menu.addAction(self.action_settings)
|
||||||
|
self.context_menu.addAction(self.template_menu.menuAction())
|
||||||
|
self.context_menu.addAction(self.network_menu.menuAction())
|
||||||
self.context_menu.addAction(self.action_editfwrules)
|
self.context_menu.addAction(self.action_editfwrules)
|
||||||
self.context_menu.addAction(self.action_appmenus)
|
self.context_menu.addAction(self.action_appmenus)
|
||||||
self.context_menu.addAction(self.action_set_keyboard_layout)
|
self.context_menu.addAction(self.action_set_keyboard_layout)
|
||||||
@ -883,6 +955,33 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
|
|
||||||
progress.setValue(row_no)
|
progress.setValue(row_no)
|
||||||
|
|
||||||
|
def init_template_menu(self):
|
||||||
|
self.template_menu.clear()
|
||||||
|
for vm in self.qubes_app.domains:
|
||||||
|
if vm.klass == 'TemplateVM':
|
||||||
|
action = self.template_menu.addAction(vm.name)
|
||||||
|
action.setData(vm.name)
|
||||||
|
action.triggered.connect(partial(self.change_template, vm.name))
|
||||||
|
|
||||||
|
def _get_default_netvm(self):
|
||||||
|
for vm in self.qubes_app.domains:
|
||||||
|
if vm.klass == 'AppVM':
|
||||||
|
return vm.property_get_default('netvm')
|
||||||
|
|
||||||
|
def init_network_menu(self):
|
||||||
|
default = self._get_default_netvm()
|
||||||
|
self.network_menu.clear()
|
||||||
|
action = self.network_menu.addAction("None")
|
||||||
|
action.triggered.connect(partial(self.change_network, None))
|
||||||
|
action = self.network_menu.addAction("default ({0})".format(default))
|
||||||
|
action.triggered.connect(partial(self.change_network, 'default'))
|
||||||
|
|
||||||
|
for vm in self.qubes_app.domains:
|
||||||
|
if vm.qid != 0 and vm.provides_network:
|
||||||
|
action = self.network_menu.addAction(vm.name)
|
||||||
|
action.setData(vm.name)
|
||||||
|
action.triggered.connect(partial(self.change_network, vm.name))
|
||||||
|
|
||||||
def setup_application(self):
|
def setup_application(self):
|
||||||
self.qt_app.setApplicationName(self.tr("Qube Manager"))
|
self.qt_app.setApplicationName(self.tr("Qube Manager"))
|
||||||
self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager"))
|
self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager"))
|
||||||
@ -940,12 +1039,16 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
domain = self.qubes_app.domains[vm]
|
domain = self.qubes_app.domains[vm]
|
||||||
self.qubes_cache.add_vm(domain)
|
self.qubes_cache.add_vm(domain)
|
||||||
self.proxy.invalidate()
|
self.proxy.invalidate()
|
||||||
|
if domain.klass == 'TemplateVM':
|
||||||
|
self.init_template_menu()
|
||||||
except (exc.QubesException, KeyError):
|
except (exc.QubesException, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_domain_removed(self, _submitter, _event, **kwargs):
|
def on_domain_removed(self, _submitter, _event, **kwargs):
|
||||||
self.qubes_cache.remove_vm(name=kwargs['vm'])
|
self.qubes_cache.remove_vm(name=kwargs['vm'])
|
||||||
self.proxy.invalidate()
|
self.proxy.invalidate()
|
||||||
|
self.init_template_menu()
|
||||||
|
self.init_network_menu()
|
||||||
|
|
||||||
def on_domain_status_changed(self, vm, event, **_kwargs):
|
def on_domain_status_changed(self, vm, event, **_kwargs):
|
||||||
try:
|
try:
|
||||||
@ -975,6 +1078,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if event.endswith(':provides_network'):
|
||||||
|
self.init_network_menu()
|
||||||
self.qubes_cache.get_vm(qid=vm.qid).update(event=event)
|
self.qubes_cache.get_vm(qid=vm.qid).update(event=event)
|
||||||
self.proxy.invalidate()
|
self.proxy.invalidate()
|
||||||
except exc.QubesDaemonAccessError:
|
except exc.QubesDaemonAccessError:
|
||||||
@ -1059,6 +1164,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
def table_selection_changed(self):
|
def table_selection_changed(self):
|
||||||
# Since selection could have multiple domains
|
# Since selection could have multiple domains
|
||||||
# enable all first and then filter them
|
# enable all first and then filter them
|
||||||
|
self.template_menu.setEnabled(True)
|
||||||
|
self.network_menu.setEnabled(True)
|
||||||
for action in self.toolbar.actions() + self.context_menu.actions():
|
for action in self.toolbar.actions() + self.context_menu.actions():
|
||||||
action.setEnabled(True)
|
action.setEnabled(True)
|
||||||
|
|
||||||
@ -1069,17 +1176,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
['Running', 'Transient', 'Halting', 'Dying']:
|
['Running', 'Transient', 'Halting', 'Dying']:
|
||||||
self.action_resumevm.setEnabled(False)
|
self.action_resumevm.setEnabled(False)
|
||||||
self.action_removevm.setEnabled(False)
|
self.action_removevm.setEnabled(False)
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
elif vm.state['power'] == 'Paused':
|
elif vm.state['power'] == 'Paused':
|
||||||
self.action_removevm.setEnabled(False)
|
self.action_removevm.setEnabled(False)
|
||||||
self.action_pausevm.setEnabled(False)
|
self.action_pausevm.setEnabled(False)
|
||||||
self.action_set_keyboard_layout.setEnabled(False)
|
self.action_set_keyboard_layout.setEnabled(False)
|
||||||
self.action_restartvm.setEnabled(False)
|
self.action_restartvm.setEnabled(False)
|
||||||
self.action_open_console.setEnabled(False)
|
self.action_open_console.setEnabled(False)
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
elif vm.state['power'] == 'Suspend':
|
elif vm.state['power'] == 'Suspend':
|
||||||
self.action_set_keyboard_layout.setEnabled(False)
|
self.action_set_keyboard_layout.setEnabled(False)
|
||||||
self.action_removevm.setEnabled(False)
|
self.action_removevm.setEnabled(False)
|
||||||
self.action_pausevm.setEnabled(False)
|
self.action_pausevm.setEnabled(False)
|
||||||
self.action_open_console.setEnabled(False)
|
self.action_open_console.setEnabled(False)
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
elif vm.state['power'] == 'Halted':
|
elif vm.state['power'] == 'Halted':
|
||||||
self.action_set_keyboard_layout.setEnabled(False)
|
self.action_set_keyboard_layout.setEnabled(False)
|
||||||
self.action_pausevm.setEnabled(False)
|
self.action_pausevm.setEnabled(False)
|
||||||
@ -1102,9 +1212,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
self.action_editfwrules.setEnabled(False)
|
self.action_editfwrules.setEnabled(False)
|
||||||
self.action_set_keyboard_layout.setEnabled(False)
|
self.action_set_keyboard_layout.setEnabled(False)
|
||||||
self.action_run_command_in_vm.setEnabled(False)
|
self.action_run_command_in_vm.setEnabled(False)
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
|
self.network_menu.setEnabled(False)
|
||||||
elif vm.klass == 'DispVM':
|
elif vm.klass == 'DispVM':
|
||||||
self.action_appmenus.setEnabled(False)
|
self.action_appmenus.setEnabled(False)
|
||||||
self.action_restartvm.setEnabled(False)
|
self.action_restartvm.setEnabled(False)
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
|
elif vm.klass == 'TemplateVM':
|
||||||
|
self.template_menu.setEnabled(False)
|
||||||
|
self.network_menu.setEnabled(False)
|
||||||
|
|
||||||
if vm.vm.features.get('internal', False):
|
if vm.vm.features.get('internal', False):
|
||||||
self.action_appmenus.setEnabled(False)
|
self.action_appmenus.setEnabled(False)
|
||||||
@ -1112,6 +1228,47 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
if not vm.updateable and vm.klass != 'AdminVM':
|
if not vm.updateable and vm.klass != 'AdminVM':
|
||||||
self.action_updatevm.setEnabled(False)
|
self.action_updatevm.setEnabled(False)
|
||||||
|
|
||||||
|
self.update_template_menu()
|
||||||
|
self.update_network_menu()
|
||||||
|
|
||||||
|
def update_template_menu(self):
|
||||||
|
if not self.template_menu.isEnabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
for entry in self.template_menu.actions():
|
||||||
|
entry.setIcon(QIcon())
|
||||||
|
|
||||||
|
vms = self.get_selected_vms()
|
||||||
|
for vm in vms:
|
||||||
|
for entry in self.template_menu.actions():
|
||||||
|
if entry.data() == vm.template:
|
||||||
|
if len(vms) == 1:
|
||||||
|
entry.setIcon(QIcon(":/on.png"))
|
||||||
|
else:
|
||||||
|
entry.setIcon(QIcon(":/transient.png"))
|
||||||
|
|
||||||
|
def update_network_menu(self):
|
||||||
|
if not self.network_menu.isEnabled():
|
||||||
|
return
|
||||||
|
|
||||||
|
for entry in self.network_menu.actions():
|
||||||
|
entry.setIcon(QIcon())
|
||||||
|
|
||||||
|
if len(self.get_selected_vms()) == 1:
|
||||||
|
icon = QIcon(":/on.png")
|
||||||
|
else:
|
||||||
|
icon = QIcon(":/transient.png")
|
||||||
|
|
||||||
|
for vm in self.get_selected_vms():
|
||||||
|
if vm.netvm == "n/a":
|
||||||
|
self.network_menu.actions()[0].setIcon(QIcon(icon))
|
||||||
|
elif vm.vm.property_is_default("netvm"):
|
||||||
|
self.network_menu.actions()[1].setIcon(QIcon(icon))
|
||||||
|
else:
|
||||||
|
for entry in self.network_menu.actions():
|
||||||
|
if entry.data() == vm.netvm:
|
||||||
|
entry.setIcon(icon)
|
||||||
|
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
@pyqtSlot(name='on_action_createvm_triggered')
|
@pyqtSlot(name='on_action_createvm_triggered')
|
||||||
def action_createvm_triggered(self):
|
def action_createvm_triggered(self):
|
||||||
@ -1200,7 +1357,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
|
|
||||||
self.start_vm(vm)
|
self.start_vm(vm)
|
||||||
|
|
||||||
def start_vm(self, vm):
|
def start_vm(self, vm, wait=False):
|
||||||
if manager_utils.is_running(vm, False):
|
if manager_utils.is_running(vm, False):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1209,6 +1366,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
thread.finished.connect(self.clear_threads)
|
thread.finished.connect(self.clear_threads)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
if wait:
|
||||||
|
with common_threads.busy_cursor():
|
||||||
|
thread.wait()
|
||||||
|
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
@pyqtSlot(name='on_action_startvm_tools_install_triggered')
|
@pyqtSlot(name='on_action_startvm_tools_install_triggered')
|
||||||
# TODO: replace with boot from device
|
# TODO: replace with boot from device
|
||||||
@ -1579,7 +1740,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
|
|||||||
self,
|
self,
|
||||||
self.tr("Error"),
|
self.tr("Error"),
|
||||||
self.tr(
|
self.tr(
|
||||||
"No log files where found for the current selection."))
|
"No log files were found for the selected qubes."))
|
||||||
|
|
||||||
except exc.QubesDaemonAccessError:
|
except exc.QubesDaemonAccessError:
|
||||||
pass
|
pass
|
||||||
|
@ -432,6 +432,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
|
|||||||
self.netVM.setCurrentIndex(-1)
|
self.netVM.setCurrentIndex(-1)
|
||||||
|
|
||||||
self.netVM.currentIndexChanged.connect(self.check_warn_dispvmnetvm)
|
self.netVM.currentIndexChanged.connect(self.check_warn_dispvmnetvm)
|
||||||
|
self.netVM.currentIndexChanged.connect(self.check_warn_templatenetvm)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.include_in_backups.setChecked(self.vm.include_in_backups)
|
self.include_in_backups.setChecked(self.vm.include_in_backups)
|
||||||
@ -625,6 +626,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
|
|||||||
self.init_mem.value() * 10 < self.max_mem_size.value():
|
self.init_mem.value() * 10 < self.max_mem_size.value():
|
||||||
self.warn_too_much_mem_label.setVisible(True)
|
self.warn_too_much_mem_label.setVisible(True)
|
||||||
|
|
||||||
|
def check_warn_templatenetvm(self):
|
||||||
|
if self.vm.klass == 'TemplateVM':
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
self.tr("Warning!"),
|
||||||
|
self.tr("Connecting a TemplateVM directly to a network is higly"
|
||||||
|
" discouraged! <br> <small>You are breaking a basic par"
|
||||||
|
"t of Qubes security and there is probably no real need"
|
||||||
|
" to do so. Continue at your own risk.</small>"))
|
||||||
|
|
||||||
def check_warn_dispvmnetvm(self):
|
def check_warn_dispvmnetvm(self):
|
||||||
if not hasattr(self.vm, 'default_dispvm'):
|
if not hasattr(self.vm, 'default_dispvm'):
|
||||||
self.warn_netvm_dispvm.setVisible(False)
|
self.warn_netvm_dispvm.setVisible(False)
|
||||||
|
@ -171,8 +171,7 @@ class NewVmTest(unittest.TestCase):
|
|||||||
self.dialog.name.setText("test-vm")
|
self.dialog.name.setText("test-vm")
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "standalone" in opt_text and "template" in opt_text and\
|
if "standalone" in opt_text:
|
||||||
"not based" not in opt_text and "empty" not in opt_text:
|
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -187,10 +186,11 @@ class NewVmTest(unittest.TestCase):
|
|||||||
self.dialog.name.setText("test-vm")
|
self.dialog.name.setText("test-vm")
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "standalone" in opt_text and\
|
if "standalone" in opt_text:
|
||||||
("not based" in opt_text or "empty" in opt_text):
|
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
# select "(none)" template
|
||||||
|
self.dialog.template_vm.setCurrentIndex(self.dialog.template_vm.count()-1)
|
||||||
|
|
||||||
self.__click_ok()
|
self.__click_ok()
|
||||||
self.mock_thread.assert_called_once_with(
|
self.mock_thread.assert_called_once_with(
|
||||||
@ -210,10 +210,11 @@ class NewVmTest(unittest.TestCase):
|
|||||||
|
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "standalone" in opt_text and\
|
if "standalone" in opt_text:
|
||||||
("not based" in opt_text or "empty" in opt_text):
|
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
# select "(none)" template
|
||||||
|
self.dialog.template_vm.setCurrentIndex(self.dialog.template_vm.count()-1)
|
||||||
|
|
||||||
self.dialog.install_system.setChecked(False)
|
self.dialog.install_system.setChecked(False)
|
||||||
|
|
||||||
@ -232,7 +233,7 @@ class NewVmTest(unittest.TestCase):
|
|||||||
# cannot install system on a template-based appvm
|
# cannot install system on a template-based appvm
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "appvm" in opt_text and "standalone" not in opt_text:
|
if "appvm" in opt_text:
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
self.assertFalse(self.dialog.install_system.isEnabled())
|
self.assertFalse(self.dialog.install_system.isEnabled())
|
||||||
@ -242,24 +243,26 @@ class NewVmTest(unittest.TestCase):
|
|||||||
# or on a standalone vm cloned from a template
|
# or on a standalone vm cloned from a template
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "standalone" in opt_text and "template" in opt_text and\
|
if "standalone" in opt_text:
|
||||||
"not based" not in opt_text and "empty" not in opt_text:
|
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
# select default template
|
||||||
|
self.dialog.template_vm.setCurrentIndex(0)
|
||||||
self.assertFalse(self.dialog.install_system.isEnabled())
|
self.assertFalse(self.dialog.install_system.isEnabled())
|
||||||
self.assertTrue(self.dialog.launch_settings.isEnabled())
|
self.assertTrue(self.dialog.launch_settings.isEnabled())
|
||||||
self.assertTrue(self.dialog.template_vm.isEnabled())
|
self.assertTrue(self.dialog.template_vm.isEnabled())
|
||||||
|
|
||||||
# cannot set a template but can install system on a truly empty AppVM
|
# can install system on a truly empty AppVM
|
||||||
for i in range(self.dialog.vm_type.count()):
|
for i in range(self.dialog.vm_type.count()):
|
||||||
opt_text = self.dialog.vm_type.itemText(i).lower()
|
opt_text = self.dialog.vm_type.itemText(i).lower()
|
||||||
if "standalone" in opt_text and\
|
if "standalone" in opt_text:
|
||||||
("not based" in opt_text or "empty" in opt_text):
|
|
||||||
self.dialog.vm_type.setCurrentIndex(i)
|
self.dialog.vm_type.setCurrentIndex(i)
|
||||||
break
|
break
|
||||||
|
self.assertTrue(self.dialog.template_vm.isEnabled())
|
||||||
|
# select "(none)" template
|
||||||
|
self.dialog.template_vm.setCurrentIndex(self.dialog.template_vm.count()-1)
|
||||||
self.assertTrue(self.dialog.install_system.isEnabled())
|
self.assertTrue(self.dialog.install_system.isEnabled())
|
||||||
self.assertTrue(self.dialog.launch_settings.isEnabled())
|
self.assertTrue(self.dialog.launch_settings.isEnabled())
|
||||||
self.assertFalse(self.dialog.template_vm.isEnabled())
|
|
||||||
|
|
||||||
def __click_ok(self):
|
def __click_ok(self):
|
||||||
okwidget = self.dialog.buttonBox.button(
|
okwidget = self.dialog.buttonBox.button(
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#
|
#
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import functools
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import unittest
|
import unittest
|
||||||
import unittest.mock
|
import unittest.mock
|
||||||
@ -41,6 +42,24 @@ from qubesmanager.tests import init_qtapp
|
|||||||
icon_size = qube_manager.icon_size
|
icon_size = qube_manager.icon_size
|
||||||
|
|
||||||
|
|
||||||
|
def listen_for_events(func):
|
||||||
|
"""Wrapper for a test that needs events listener to be registered all the time.
|
||||||
|
Note the test still needs to yield to the event loop to actually handle events.
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
events_listener = \
|
||||||
|
asyncio.ensure_future(self.dispatcher.listen_for_events())
|
||||||
|
# let it connect (run until first yield/await)
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
try:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
events_listener.cancel()
|
||||||
|
self.loop.call_soon(self.loop.stop)
|
||||||
|
self.loop.run_forever()
|
||||||
|
return wrapper
|
||||||
|
|
||||||
class QubeManagerTest(unittest.TestCase):
|
class QubeManagerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(QubeManagerTest, self).setUp()
|
super(QubeManagerTest, self).setUp()
|
||||||
@ -156,10 +175,8 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
for row in range(self.dialog.table.model().rowCount()):
|
for row in range(self.dialog.table.model().rowCount()):
|
||||||
vm = self._get_table_vm(row)
|
vm = self._get_table_vm(row)
|
||||||
|
|
||||||
incl_backups_item = self._get_table_item(row, "Backup",
|
incl_backups_item = self._get_table_item(row, "Backup", Qt.CheckStateRole) == Qt.Checked
|
||||||
Qt.CheckStateRole)
|
|
||||||
incl_backups_value = getattr(vm, 'include_in_backups', False)
|
incl_backups_value = getattr(vm, 'include_in_backups', False)
|
||||||
incl_backups_value = Qt.Checked if incl_backups_value else Qt.Unchecked
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
incl_backups_value, incl_backups_item,
|
incl_backups_value, incl_backups_item,
|
||||||
@ -187,7 +204,7 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
def_dispvm_item = self._get_table_item(row, "Default DispVM")
|
def_dispvm_item = self._get_table_item(row, "Default DispVM")
|
||||||
if vm.property_is_default("default_dispvm"):
|
if vm.property_is_default("default_dispvm"):
|
||||||
def_dispvm_value = "default ({})".format(
|
def_dispvm_value = "default ({})".format(
|
||||||
self.qapp.default_dispvm)
|
vm.property_get_default("default_dispvm"))
|
||||||
else:
|
else:
|
||||||
def_dispvm_value = getattr(vm, "default_dispvm", None)
|
def_dispvm_value = getattr(vm, "default_dispvm", None)
|
||||||
|
|
||||||
@ -314,6 +331,8 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
def test_204_vm_keyboard(self, mock_message):
|
def test_204_vm_keyboard(self, mock_message):
|
||||||
selected_vm = self._select_non_admin_vm(running=True)
|
selected_vm = self._select_non_admin_vm(running=True)
|
||||||
self.assertIsNotNone(selected_vm, "No valid non-admin VM found")
|
self.assertIsNotNone(selected_vm, "No valid non-admin VM found")
|
||||||
|
if 'supported-feature.keyboard-layout' not in selected_vm.features:
|
||||||
|
self.skipTest("VM {!s} does not support new layout change".format(selected_vm))
|
||||||
widget = self.dialog.toolbar.widgetForAction(
|
widget = self.dialog.toolbar.widgetForAction(
|
||||||
self.dialog.action_set_keyboard_layout)
|
self.dialog.action_set_keyboard_layout)
|
||||||
with unittest.mock.patch.object(selected_vm, 'run') as mock_run:
|
with unittest.mock.patch.object(selected_vm, 'run') as mock_run:
|
||||||
@ -334,9 +353,6 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
QtCore.Qt.LeftButton)
|
QtCore.Qt.LeftButton)
|
||||||
self.assertEqual(mock_run.call_count, 0,
|
self.assertEqual(mock_run.call_count, 0,
|
||||||
"Keyboard change called on a halted VM")
|
"Keyboard change called on a halted VM")
|
||||||
self.assertEqual(mock_message.call_count, 0,
|
|
||||||
"Keyboard change called on a halted VM with"
|
|
||||||
" obsolete keyboard-layout handling")
|
|
||||||
|
|
||||||
def test_206_dom0_keyboard(self):
|
def test_206_dom0_keyboard(self):
|
||||||
self._select_admin_vm()
|
self._select_admin_vm()
|
||||||
@ -715,6 +731,241 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
self.assertEqual(expected_number, actual_number,
|
self.assertEqual(expected_number, actual_number,
|
||||||
"Incorrect number of vms shown for cleared search box")
|
"Incorrect number of vms shown for cleared search box")
|
||||||
|
|
||||||
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question')
|
||||||
|
@listen_for_events
|
||||||
|
def test_240_network_menu_single(self, mock_question):
|
||||||
|
mock_question.return_value = QtWidgets.QMessageBox.Yes
|
||||||
|
target_vm_name = 'work'
|
||||||
|
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', '-D', target_vm_name, 'netvm'], timeout=20)
|
||||||
|
self._select_vms(['work'])
|
||||||
|
selected_vm = self.qapp.domains[target_vm_name]
|
||||||
|
# reset to default even in case of failure
|
||||||
|
self.addCleanup(functools.partial(delattr, selected_vm, 'netvm'))
|
||||||
|
|
||||||
|
# this is the method to get '==' operator working on icons...
|
||||||
|
on_icon = QIcon(":/on.png").pixmap(64).toImage()
|
||||||
|
off_icon = QIcon().pixmap(64).toImage()
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text().startswith('default '):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), on_icon)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('default netvm not found')
|
||||||
|
|
||||||
|
# change to specific value
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text() == 'sys-net':
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('sys-net netvm not found')
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
mock_question.assert_called()
|
||||||
|
self.assertEqual(str(selected_vm.netvm), 'sys-net')
|
||||||
|
mock_question.reset_mock()
|
||||||
|
|
||||||
|
# change to none
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text() == 'None':
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('"none" netvm not found')
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
mock_question.assert_called()
|
||||||
|
self.assertIsNone(selected_vm.netvm)
|
||||||
|
mock_question.reset_mock()
|
||||||
|
|
||||||
|
# then go back to the default
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text().startswith('default '):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
|
||||||
|
mock_question.assert_called()
|
||||||
|
self.assertTrue(selected_vm.property_is_default('netvm'))
|
||||||
|
|
||||||
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question')
|
||||||
|
@listen_for_events
|
||||||
|
def test_241_network_menu_multiple(self, mock_question):
|
||||||
|
mock_question.return_value = QtWidgets.QMessageBox.Yes
|
||||||
|
target_vm_names = ['work', 'personal', 'vault']
|
||||||
|
work = self.qapp.domains['work']
|
||||||
|
personal = self.qapp.domains['personal']
|
||||||
|
vault = self.qapp.domains['vault']
|
||||||
|
# reset to default even in case of failure
|
||||||
|
self.addCleanup(functools.partial(delattr, work, 'netvm'))
|
||||||
|
self.addCleanup(functools.partial(delattr, personal, 'netvm'))
|
||||||
|
self.addCleanup(functools.partial(setattr, vault, 'netvm', None))
|
||||||
|
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', '-D', 'work', 'netvm'], timeout=5)
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', '-D', 'personal', 'netvm'], timeout=5)
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', 'vault', ''], timeout=5)
|
||||||
|
self._select_vms(target_vm_names)
|
||||||
|
|
||||||
|
# this is the method to get '==' operator working on icons...
|
||||||
|
on_icon = QIcon(":/on.png").pixmap(64).toImage()
|
||||||
|
transient_icon = QIcon(":/transient.png").pixmap(64).toImage()
|
||||||
|
off_icon = QIcon().pixmap(64).toImage()
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text().startswith('default '):
|
||||||
|
# work, personal
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), transient_icon)
|
||||||
|
elif action.text() == 'None':
|
||||||
|
# vault
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), transient_icon)
|
||||||
|
else:
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
|
||||||
|
# change to specific value
|
||||||
|
for action in self.dialog.network_menu.actions():
|
||||||
|
if action.text() == 'sys-net':
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('sys-net netvm not found')
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
mock_question.assert_called()
|
||||||
|
self.assertEqual(str(work.netvm), 'sys-net')
|
||||||
|
self.assertEqual(str(personal.netvm), 'sys-net')
|
||||||
|
self.assertEqual(str(vault.netvm), 'sys-net')
|
||||||
|
mock_question.reset_mock()
|
||||||
|
|
||||||
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question')
|
||||||
|
@listen_for_events
|
||||||
|
def test_250_template_menu_single(self, mock_question):
|
||||||
|
mock_question.return_value = QtWidgets.QMessageBox.Yes
|
||||||
|
target_vm_name = 'work'
|
||||||
|
selected_vm = self.qapp.domains[target_vm_name]
|
||||||
|
if selected_vm.is_running():
|
||||||
|
self.skipTest(
|
||||||
|
'VM {!s} is running, please stop it first'.format(selected_vm))
|
||||||
|
current_template = selected_vm.template
|
||||||
|
new_template = self._select_templatevm(
|
||||||
|
different_than=[str(current_template)])
|
||||||
|
|
||||||
|
self._select_vms(['work'])
|
||||||
|
|
||||||
|
# reset to previous value even in case of failure
|
||||||
|
self.addCleanup(functools.partial(
|
||||||
|
setattr, selected_vm, 'template', str(current_template)))
|
||||||
|
|
||||||
|
# this is the method to get '==' operator working on icons...
|
||||||
|
on_icon = QIcon(":/on.png").pixmap(64).toImage()
|
||||||
|
off_icon = QIcon().pixmap(64).toImage()
|
||||||
|
found = False
|
||||||
|
for action in self.dialog.template_menu.actions():
|
||||||
|
if action.text() == str(current_template):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), on_icon)
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
self.fail(
|
||||||
|
'current template value ({!s}) not found in the menu'.format(
|
||||||
|
current_template))
|
||||||
|
|
||||||
|
# change to specific value
|
||||||
|
for action in self.dialog.template_menu.actions():
|
||||||
|
if action.text() == str(new_template):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('template {!s} not found in the menu'.format(new_template))
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
mock_question.assert_called()
|
||||||
|
# compare str(), to have better error message on mismatch
|
||||||
|
self.assertEqual(str(selected_vm.template), str(new_template))
|
||||||
|
mock_question.reset_mock()
|
||||||
|
|
||||||
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.question')
|
||||||
|
@listen_for_events
|
||||||
|
def test_251_template_menu_multiple(self, mock_question):
|
||||||
|
mock_question.return_value = QtWidgets.QMessageBox.Yes
|
||||||
|
target_vm_names = ['work', 'personal', 'untrusted']
|
||||||
|
work = self.qapp.domains['work']
|
||||||
|
personal = self.qapp.domains['personal']
|
||||||
|
untrusted = self.qapp.domains['untrusted']
|
||||||
|
if any(vm.is_running() for vm in [work, personal, untrusted]):
|
||||||
|
self.skipTest('Any of work, personal, untrusted VM is running')
|
||||||
|
|
||||||
|
old_template = work.template
|
||||||
|
new_template = self._select_templatevm(
|
||||||
|
different_than=[str(work.template),
|
||||||
|
str(personal.template),
|
||||||
|
str(untrusted.template)])
|
||||||
|
# reset to previous value even in case of failure
|
||||||
|
self.addCleanup(functools.partial(
|
||||||
|
setattr, work, 'template', str(work.template)))
|
||||||
|
self.addCleanup(functools.partial(
|
||||||
|
setattr, personal, 'template', str(personal.template)))
|
||||||
|
self.addCleanup(functools.partial(
|
||||||
|
setattr, untrusted, 'template', str(untrusted.template)))
|
||||||
|
|
||||||
|
# set all to the same value
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', 'personal', 'template', str(work.template)], timeout=5)
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', 'untrusted', 'template', str(work.template)], timeout=5)
|
||||||
|
|
||||||
|
self._select_vms(target_vm_names)
|
||||||
|
|
||||||
|
# this is the method to get '==' operator working on icons...
|
||||||
|
on_icon = QIcon(":/on.png").pixmap(64).toImage()
|
||||||
|
transient_icon = QIcon(":/transient.png").pixmap(64).toImage()
|
||||||
|
off_icon = QIcon().pixmap(64).toImage()
|
||||||
|
for action in self.dialog.template_menu.actions():
|
||||||
|
if action.text() == str(old_template):
|
||||||
|
self.assertIn(
|
||||||
|
action.icon().pixmap(64).toImage(),
|
||||||
|
(on_icon, transient_icon))
|
||||||
|
else:
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
|
||||||
|
# make one different
|
||||||
|
self._run_command_and_process_events(
|
||||||
|
['qvm-prefs', 'work', 'template', str(new_template)], timeout=5)
|
||||||
|
|
||||||
|
for action in self.dialog.template_menu.actions():
|
||||||
|
if action.text() == str(old_template):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), transient_icon)
|
||||||
|
elif action.text() == str(new_template):
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), transient_icon)
|
||||||
|
else:
|
||||||
|
self.assertEqual(action.icon().pixmap(64).toImage(), off_icon)
|
||||||
|
|
||||||
|
# change all to the same value
|
||||||
|
for action in self.dialog.template_menu.actions():
|
||||||
|
if action.text() == str(new_template):
|
||||||
|
action.trigger()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fail('{!s} template not found'.format(new_template))
|
||||||
|
# process events
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0))
|
||||||
|
mock_question.assert_called()
|
||||||
|
self.assertEqual(str(work.template), str(new_template))
|
||||||
|
self.assertEqual(str(personal.template), str(new_template))
|
||||||
|
self.assertEqual(str(untrusted.template), str(new_template))
|
||||||
|
|
||||||
|
|
||||||
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.information')
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.information')
|
||||||
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning')
|
@unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning')
|
||||||
def test_300_clear_threads(self, mock_warning, mock_info):
|
def test_300_clear_threads(self, mock_warning, mock_info):
|
||||||
@ -1133,27 +1384,31 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
self.assertEqual(call_count, 0)
|
self.assertEqual(call_count, 0)
|
||||||
|
|
||||||
@unittest.mock.patch('qubesmanager.log_dialog.LogDialog')
|
@unittest.mock.patch('qubesmanager.log_dialog.LogDialog')
|
||||||
def test_500_logs(self, mock_logDialog):
|
def test_500_logs(self, mock_log_dialog):
|
||||||
self._select_admin_vm()
|
self._select_admin_vm()
|
||||||
|
|
||||||
self.assertTrue(self.dialog.action_show_logs.isEnabled())
|
|
||||||
self.dialog.action_show_logs.trigger()
|
self.dialog.action_show_logs.trigger()
|
||||||
dom0_logs = mock_logDialog.call_args.args[1]
|
mock_log_dialog.assert_called_once()
|
||||||
self.assertIn('/var/log/xen/console/hypervisor.log', dom0_logs,
|
dom0_logs = mock_log_dialog.mock_calls[0][1][1]
|
||||||
"Log for dom0 does not contain 'hypervisor'")
|
for c in dom0_logs:
|
||||||
|
self.assertIn("hypervisor", c,
|
||||||
|
"Log for dom0 does not contain 'hypervisor'")
|
||||||
|
|
||||||
|
mock_log_dialog.reset_mock()
|
||||||
|
|
||||||
selected_vm = self._select_non_admin_vm(running=True).name
|
selected_vm = self._select_non_admin_vm(running=True).name
|
||||||
|
|
||||||
self.assertTrue(self.dialog.action_show_logs.isEnabled())
|
|
||||||
self.dialog.action_show_logs.trigger()
|
self.dialog.action_show_logs.trigger()
|
||||||
vm_logs = mock_logDialog.call_args.args[1]
|
mock_log_dialog.assert_called_once()
|
||||||
self.assertIn(
|
vm_logs = mock_log_dialog.mock_calls[0][1][1]
|
||||||
selected_vm,
|
for c in vm_logs:
|
||||||
",".join(vm_logs),
|
self.assertIn(
|
||||||
"Log for {} does not contain its name".format(selected_vm))
|
selected_vm,
|
||||||
|
c,
|
||||||
|
"Log for {} does not contain its name".format(selected_vm))
|
||||||
|
|
||||||
self.assertNotEqual(dom0_logs, vm_logs,
|
self.assertNotEqual(dom0_logs, vm_logs,
|
||||||
"Same logs found for dom0 and non-adminVM")
|
"Same logs found for dom0 and non-adminVM")
|
||||||
|
|
||||||
def _find_vm_row(self, vm_name):
|
def _find_vm_row(self, vm_name):
|
||||||
for row in range(self.dialog.table.model().rowCount()):
|
for row in range(self.dialog.table.model().rowCount()):
|
||||||
@ -1227,7 +1482,7 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
for row in range(self.dialog.table.model().rowCount()):
|
for row in range(self.dialog.table.model().rowCount()):
|
||||||
template = self._get_table_item(row, "Template")
|
template = self._get_table_item(row, "Template")
|
||||||
vm = self._get_table_vm(row)
|
vm = self._get_table_vm(row)
|
||||||
if template != 'AdminVM' and \
|
if template != 'AdminVM' and not vm.provides_network and \
|
||||||
(running is None
|
(running is None
|
||||||
or (running and vm.is_running())
|
or (running and vm.is_running())
|
||||||
or (not running and not vm.is_running())):
|
or (not running and not vm.is_running())):
|
||||||
@ -1236,19 +1491,28 @@ class QubeManagerTest(unittest.TestCase):
|
|||||||
return vm
|
return vm
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _select_templatevm(self, running=None):
|
def _select_templatevm(self, running=None, different_than=()):
|
||||||
for row in range(self.dialog.table.model().rowCount()):
|
for row in range(self.dialog.table.model().rowCount()):
|
||||||
template = self._get_table_item(row, "Template")
|
template = self._get_table_item(row, "Template")
|
||||||
vm = self._get_table_vm(row)
|
vm = self._get_table_vm(row)
|
||||||
if template == 'TemplateVM' and \
|
if template == 'TemplateVM' and \
|
||||||
|
(template not in different_than) and \
|
||||||
(running is None
|
(running is None
|
||||||
or (running and vm.is_running())
|
or (bool(running) == bool(vm.is_running()))):
|
||||||
or (not running and not vm.is_running())):
|
|
||||||
index = self.dialog.table.model().index(row, 0)
|
index = self.dialog.table.model().index(row, 0)
|
||||||
self.dialog.table.setCurrentIndex(index)
|
self.dialog.table.setCurrentIndex(index)
|
||||||
return vm
|
return vm
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _select_vms(self, vms: list):
|
||||||
|
self.dialog.table.selectionModel().clear()
|
||||||
|
mode = QtCore.QItemSelectionModel.Select | QtCore.QItemSelectionModel.Rows
|
||||||
|
for row in range(self.dialog.table.model().rowCount()):
|
||||||
|
vm = self._get_table_vm(row)
|
||||||
|
if str(vm) in vms:
|
||||||
|
index = self.dialog.table.model().index(row, 0)
|
||||||
|
self.dialog.table.selectionModel().select(index, mode)
|
||||||
|
|
||||||
def __check_sorting(self, column_name):
|
def __check_sorting(self, column_name):
|
||||||
last_text = None
|
last_text = None
|
||||||
last_vm = None
|
last_vm = None
|
||||||
|
@ -350,6 +350,24 @@ Template</string>
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Qube</string>
|
<string>&Qube</string>
|
||||||
</property>
|
</property>
|
||||||
|
<widget class="QMenu" name="template_menu">
|
||||||
|
<property name="title">
|
||||||
|
<string>Template</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../resources.qrc">
|
||||||
|
<normaloff>:/templatevm.png</normaloff>:/templatevm.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="network_menu">
|
||||||
|
<property name="title">
|
||||||
|
<string>Network</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/netvm.png</normaloff>:/netvm.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
<addaction name="action_createvm"/>
|
<addaction name="action_createvm"/>
|
||||||
<addaction name="action_removevm"/>
|
<addaction name="action_removevm"/>
|
||||||
<addaction name="action_clonevm"/>
|
<addaction name="action_clonevm"/>
|
||||||
@ -361,6 +379,8 @@ Template</string>
|
|||||||
<addaction name="action_killvm"/>
|
<addaction name="action_killvm"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_settings"/>
|
<addaction name="action_settings"/>
|
||||||
|
<addaction name="template_menu"/>
|
||||||
|
<addaction name="network_menu"/>
|
||||||
<addaction name="action_editfwrules"/>
|
<addaction name="action_editfwrules"/>
|
||||||
<addaction name="action_appmenus"/>
|
<addaction name="action_appmenus"/>
|
||||||
<addaction name="action_updatevm"/>
|
<addaction name="action_updatevm"/>
|
||||||
|
@ -981,11 +981,17 @@ The qube must be running to disable seamless mode; this setting is not persisten
|
|||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLabel" name="kernel_opts">
|
<widget class="QLabel" name="kernel_opts">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
<sizepolicy hsizetype="Maximum" vsizetype="Minimum">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>250</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<weight>50</weight>
|
<weight>50</weight>
|
||||||
@ -996,6 +1002,9 @@ The qube must be running to disable seamless mode; this setting is not persisten
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>[]</string>
|
<string>[]</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
Loading…
Reference in New Issue
Block a user