Added AdminAPI events to Qube Manager

rewritten parts of Qube Manager to use AdminAPI events (with quamash
to combine PyQt with asyncio).

hopefully fixes QubesOS/qubes-issues#4116
hopefully fixes QubesOS/qubes-issues#4258
hopefully fixes QubesOS/qubes-issues#4244
obsoletes/fixes QubesOS/qubes-issues#4190
hopefully fixes QubesOS/qubes-issues#3675
This commit is contained in:
Marta Marczykowska-Górecka 2018-09-10 17:48:14 +02:00
parent 20ee51f3d1
commit 4fc295e6a7
No known key found for this signature in database
GPG Key ID: 9A752C30B26FD04B

View File

@ -21,7 +21,6 @@
# with this program; if not, see <http://www.gnu.org/licenses/>. # with this program; if not, see <http://www.gnu.org/licenses/>.
# #
# #
import sys import sys
import os import os
import os.path import os.path
@ -30,12 +29,14 @@ import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
import traceback import traceback
import threading import threading
import quamash
from pydbus import SessionBus import asyncio
from contextlib import suppress
from qubesadmin import Qubes from qubesadmin import Qubes
from qubesadmin import exc from qubesadmin import exc
from qubesadmin import utils from qubesadmin import utils
from qubesadmin import events
from PyQt4 import QtGui # pylint: disable=import-error from PyQt4 import QtGui # pylint: disable=import-error
from PyQt4 import QtCore # pylint: disable=import-error from PyQt4 import QtCore # pylint: disable=import-error
@ -257,7 +258,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
"Last backup": 10, "Last backup": 10,
} }
def __init__(self, qt_app, qubes_app, parent=None): def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
# pylint: disable=unused-argument # pylint: disable=unused-argument
super(VmManagerWindow, self).__init__() super(VmManagerWindow, self).__init__()
self.setupUi(self) self.setupUi(self)
@ -390,15 +391,24 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
self.shutdown_monitor = {} self.shutdown_monitor = {}
# Connect dbus events # Connect dbus events
self.bus = SessionBus() self.dispatcher = dispatcher
manager = self.bus.get("org.qubes.DomainManager1") dispatcher.add_handler('domain-pre-start',
manager.DomainAdded.connect(self.on_domain_added) self.on_domain_status_changed)
manager.DomainRemoved.connect(self.on_domain_removed) dispatcher.add_handler('domain-start', self.on_domain_status_changed)
manager.Failed.connect(self.on_failed) dispatcher.add_handler('domain-start-failed',
manager.Halted.connect(self.on_halted) self.on_domain_status_changed)
manager.Halting.connect(self.on_halting) dispatcher.add_handler('domain-stopped', self.on_domain_status_changed)
manager.Starting.connect(self.on_starting) dispatcher.add_handler('domain-shutdown', self.on_domain_status_changed)
manager.Started.connect(self.on_started)
dispatcher.add_handler('domain-add', self.on_domain_added)
dispatcher.add_handler('domain-delete', self.on_domain_removed)
dispatcher.add_handler('property-set:*',
self.on_domain_changed)
dispatcher.add_handler('property-del:*',
self.on_domain_changed)
dispatcher.add_handler('property-load',
self.on_domain_changed)
# Check Updates Timer # Check Updates Timer
timer = QtCore.QTimer(self) timer = QtCore.QTimer(self)
@ -421,19 +431,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# the VM might have vanished in the meantime # the VM might have vanished in the meantime
pass pass
def on_domain_added(self, _, domain): def on_domain_added(self, submitter, event, **kwargs):
#needs to clear cache
self.qubes_app.domains.clear_cache()
qid = int(domain.split('/')[-1])
self.table.setSortingEnabled(False) self.table.setSortingEnabled(False)
row_no = self.table.rowCount() row_no = self.table.rowCount()
self.table.setRowCount(row_no + 1) self.table.setRowCount(row_no + 1)
for vm in self.qubes_app.domains: for vm in self.qubes_app.domains:
if vm.qid == qid: if vm == kwargs['vm']:
vm_row = VmRowInTable(vm, row_no, self.table) vm_row = VmRowInTable(vm, row_no, self.table)
self.vms_in_table[vm.qid] = vm_row self.vms_in_table[vm.qid] = vm_row
self.table.setSortingEnabled(True) self.table.setSortingEnabled(True)
@ -443,73 +449,33 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
# Never should reach here # Never should reach here
raise RuntimeError('Added domain not found') raise RuntimeError('Added domain not found')
def on_domain_removed(self, _, domain): def on_domain_removed(self, submitter, event, **kwargs):
#needs to clear cache
self.qubes_app.domains.clear_cache()
qid = int(domain.split('/')[-1])
# Find row and remove row_to_delete = None
try: qid_to_delete = None
row_index = 0 for qid, row in self.vms_in_table.items():
vm_item = self.table.item(row_index, self.columns_indices["Name"]) if row.vm.name == kwargs['vm']:
while vm_item.qid != qid: row_to_delete = row
row_index += 1 qid_to_delete = qid
vm_item = self.table.item(row_index,\ if not row_to_delete:
self.columns_indices["Name"]) return # for some reason, the VM was removed in some other way
except:
raise RuntimeError('Deleted domain not found')
self.table.removeRow(row_index) del self.vms_in_table[qid_to_delete]
del self.vms_in_table[qid] self.table.removeRow(row_to_delete.name_widget.row())
def on_failed(self, _, domain): def on_domain_status_changed(self, vm, event, **kwargs):
qid = int(domain.split('/')[-1]) self.vms_in_table[vm.qid].info_widget.update_vm_state()
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm(): if vm == self.get_selected_vm():
self.table_selection_changed() self.table_selection_changed()
def on_halted(self, _, domain): if vm.klass == 'TemplateVM':
qid = int(domain.split('/')[-1]) for row in self.vms_in_table:
self.vms_in_table[qid].update() if row.vm.template == vm:
row.info_widget.update_vm_state()
if self.vms_in_table[qid].vm == self.get_selected_vm(): def on_domain_changed(self, vm, event, **kwargs):
self.table_selection_changed() self.vms_in_table[vm.qid].update()
# Check if is TemplatVM and update related AppVMs
starting_vm = self.vms_in_table[qid]
if starting_vm.vm.klass == 'TemplateVM':
for vm in starting_vm.vm.appvms:
if vm.klass == 'AppVM':
self.vms_in_table[vm.qid].update()
def on_halting(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
def on_started(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
def on_starting(self, _, domain):
qid = int(domain.split('/')[-1])
self.vms_in_table[qid].update()
if self.vms_in_table[qid].vm == self.get_selected_vm():
self.table_selection_changed()
# Check if is TemplatVM and update related AppVMs
starting_vm = self.vms_in_table[qid]
if starting_vm.vm.klass == 'TemplateVM':
for vm in starting_vm.vm.appvms:
if vm.klass == 'AppVM':
self.vms_in_table[vm.qid].update()
def load_manager_settings(self): def load_manager_settings(self):
# visible columns # visible columns
@ -1345,20 +1311,39 @@ def handle_exception(exc_type, exc_value, exc_traceback):
msg_box.exec_() msg_box.exec_()
def loop_shutdown():
pending = asyncio.Task.all_tasks()
for task in pending:
with suppress(asyncio.CancelledError):
task.cancel()
def main(): def main():
qt_app = QtGui.QApplication(sys.argv) qt_app = QtGui.QApplication(sys.argv)
qt_app.setOrganizationName("The Qubes Project") qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org") qt_app.setOrganizationDomain("http://qubes-os.org")
qt_app.setApplicationName("Qube Manager") qt_app.setApplicationName("Qube Manager")
qt_app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager")) qt_app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager"))
qt_app.lastWindowClosed.connect(loop_shutdown)
sys.excepthook = handle_exception
qubes_app = Qubes() qubes_app = Qubes()
manager_window = VmManagerWindow(qt_app, qubes_app) loop = quamash.QEventLoop(qt_app)
asyncio.set_event_loop(loop)
dispatcher = events.EventsDispatcher(qubes_app)
manager_window = VmManagerWindow(qt_app, qubes_app, dispatcher)
manager_window.show() manager_window.show()
qt_app.exec_()
try:
done, _ = loop.run_until_complete(
asyncio.ensure_future(dispatcher.listen_for_events()))
except asyncio.CancelledError:
pass
except Exception:
loop_shutdown()
exc_type, exc_value, exc_traceback = sys.exc_info()[:3]
handle_exception(exc_type, exc_value, exc_traceback)
if __name__ == "__main__": if __name__ == "__main__":