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:
parent
20ee51f3d1
commit
4fc295e6a7
@ -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__":
|
||||||
|
Loading…
Reference in New Issue
Block a user