2010-05-11 16:51:31 +02:00
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@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.
#
#
import sys
from PyQt4 . QtCore import *
from PyQt4 . QtGui import *
from qubes . qubes import QubesVmCollection
from qubes . qubes import QubesException
from qubes . qubes import qubes_store_filename
from qubes . qubes import QubesVmLabels
from qubes . qubes import dry_run
from qubes . qubes import qubes_guid_path
from qubes . qubes import QubesDaemonPidfile
2010-09-16 18:49:36 +02:00
from qubes . qubes import QubesHost
2010-05-11 16:51:31 +02:00
import qubesmanager . qrc_resources
import ui_newappvmdlg
2011-02-21 18:15:35 +01:00
from firewall import EditFwRulesDlg , QubesFirewallRulesModel
2010-05-11 16:51:31 +02:00
from pyinotify import WatchManager , Notifier , ThreadedNotifier , EventsCodes , ProcessEvent
import subprocess
import time
import threading
2011-03-02 14:42:11 +01:00
qubes_guid_path = ' /usr/bin/qubes_guid '
2010-05-11 16:51:31 +02:00
class QubesConfigFileWatcher ( ProcessEvent ) :
def __init__ ( self , update_func ) :
self . update_func = update_func
2011-03-07 16:48:53 +01:00
2011-03-14 22:17:28 +01:00
def process_IN_MODIFY ( self , event ) :
2010-05-11 16:51:31 +02:00
self . update_func ( )
class VmStatusIcon ( QLabel ) :
def __init__ ( self , vm , parent = None ) :
super ( VmStatusIcon , self ) . __init__ ( parent )
self . vm = vm
( icon_pixmap , icon_sz ) = self . set_vm_icon ( self . vm )
self . setPixmap ( icon_pixmap )
self . setFixedSize ( icon_sz )
2011-04-02 15:27:40 +02:00
self . previous_power_state = vm . last_power_state
2010-05-11 16:51:31 +02:00
def update ( self ) :
2011-04-02 15:27:40 +02:00
if self . previous_power_state != self . vm . last_power_state :
2010-05-11 16:51:31 +02:00
( icon_pixmap , icon_sz ) = self . set_vm_icon ( self . vm )
self . setPixmap ( icon_pixmap )
self . setFixedSize ( icon_sz )
2011-04-02 15:27:40 +02:00
self . previous_power_state = self . vm . last_power_state
2010-05-11 16:51:31 +02:00
def set_vm_icon ( self , vm ) :
if vm . qid == 0 :
icon = QIcon ( " :/dom0.png " )
elif vm . is_appvm ( ) :
icon = QIcon ( vm . label . icon_path )
2011-03-05 15:12:34 +01:00
elif vm . is_template ( ) :
2010-05-11 16:51:31 +02:00
icon = QIcon ( " :/templatevm.png " )
elif vm . is_netvm ( ) :
icon = QIcon ( " :/netvm.png " )
else :
icon = QIcon ( )
icon_sz = QSize ( VmManagerWindow . row_height * 0.8 , VmManagerWindow . row_height * 0.8 )
2011-04-02 15:27:40 +02:00
if vm . last_power_state :
2010-05-11 16:51:31 +02:00
icon_pixmap = icon . pixmap ( icon_sz )
else :
icon_pixmap = icon . pixmap ( icon_sz , QIcon . Disabled )
return ( icon_pixmap , icon_sz )
class VmInfoWidget ( QWidget ) :
def __init__ ( self , vm , parent = None ) :
super ( VmInfoWidget , self ) . __init__ ( parent )
layout0 = QHBoxLayout ( )
2011-04-03 01:21:02 +02:00
self . label_name = QLabel ( vm . name )
2010-05-11 16:51:31 +02:00
2011-04-02 15:27:40 +02:00
self . vm_running = vm . last_power_state
2011-04-03 01:21:02 +02:00
layout0 . addWidget ( self . label_name , alignment = Qt . AlignLeft )
2010-05-11 16:51:31 +02:00
layout1 = QHBoxLayout ( )
2011-04-03 01:27:42 +02:00
if vm . template_vm is not None :
self . label_tmpl = QLabel ( " <i><font color= \" gray \" > " + ( vm . template_vm . name ) + " </i></font> " )
elif vm . is_appvm ( ) : # and vm.template_vm is None
self . label_tmpl = QLabel ( " <i><font color= \" gray \" >StandaloneVM</i></font> " )
2011-03-05 15:12:34 +01:00
elif vm . is_template ( ) :
2011-04-03 01:21:02 +02:00
self . label_tmpl = QLabel ( " <i><font color= \" gray \" >TemplateVM</i></font> " )
2010-05-11 16:51:31 +02:00
elif vm . qid == 0 :
2011-04-03 01:21:02 +02:00
self . label_tmpl = QLabel ( " <i><font color= \" gray \" >AdminVM</i></font> " )
2010-05-11 16:51:31 +02:00
elif vm . is_netvm ( ) :
2011-04-03 01:21:02 +02:00
self . label_tmpl = QLabel ( " <i><font color= \" gray \" >NetVM</i></font> " )
2010-05-11 16:51:31 +02:00
else :
2011-04-03 01:21:02 +02:00
self . label_tmpl = QLabel ( " " )
2010-05-11 16:51:31 +02:00
label_icon_networked = self . set_icon ( " :/networking.png " , vm . is_networked ( ) )
layout1 . addWidget ( label_icon_networked , alignment = Qt . AlignLeft )
if vm . is_updateable ( ) :
label_icon_updtbl = self . set_icon ( " :/updateable.png " , True )
layout1 . addWidget ( label_icon_updtbl , alignment = Qt . AlignLeft )
2011-04-03 01:21:02 +02:00
layout1 . addWidget ( self . label_tmpl , alignment = Qt . AlignLeft )
2010-05-11 16:51:31 +02:00
layout1 . addStretch ( )
layout2 = QVBoxLayout ( )
layout2 . addLayout ( layout0 )
layout2 . addLayout ( layout1 )
layout3 = QHBoxLayout ( )
self . vm_icon = VmStatusIcon ( vm )
layout3 . addWidget ( self . vm_icon )
layout3 . addSpacing ( 10 )
layout3 . addLayout ( layout2 )
self . setLayout ( layout3 )
2011-04-03 01:23:28 +02:00
self . previous_outdated = False
2010-05-11 16:51:31 +02:00
def set_icon ( self , icon_path , enabled = True ) :
label_icon = QLabel ( )
icon = QIcon ( icon_path )
icon_sz = QSize ( VmManagerWindow . row_height * 0.3 , VmManagerWindow . row_height * 0.3 )
icon_pixmap = icon . pixmap ( icon_sz , QIcon . Disabled if not enabled else QIcon . Normal )
label_icon . setPixmap ( icon_pixmap )
label_icon . setFixedSize ( icon_sz )
return label_icon
def update_vm_state ( self , vm ) :
self . vm_icon . update ( )
2011-04-03 01:23:28 +02:00
def update_outdated ( self , vm ) :
outdated = vm . is_outdated ( )
if outdated != self . previous_outdated :
if outdated :
2011-04-07 13:11:58 +02:00
self . label_name . setText ( vm . name + " <small><font color= \" red \" > (outdated)</font></small> " )
2011-04-03 01:23:28 +02:00
else :
2011-04-07 12:23:01 +02:00
self . label_name . setText ( vm . name )
2011-04-07 13:02:02 +02:00
self . previous_outdated = outdated
2011-04-03 01:23:28 +02:00
2011-03-07 16:48:19 +01:00
class VmUsageWidget ( QWidget ) :
def __init__ ( self , vm , parent = None ) :
super ( VmUsageWidget , self ) . __init__ ( parent )
self . cpu_widget = QProgressBar ( )
self . mem_widget = QProgressBar ( )
self . cpu_widget . setMinimum ( 0 )
self . cpu_widget . setMaximum ( 100 )
self . mem_widget . setMinimum ( 0 )
2011-04-02 15:27:40 +02:00
self . mem_widget . setMaximum ( qubes_host . memory_total / ( 1024 * 1024 ) )
self . mem_widget . setFormat ( " % v MB " ) ;
2011-03-07 16:48:19 +01:00
self . cpu_label = QLabel ( " CPU " )
self . mem_label = QLabel ( " MEM " )
layout_cpu = QHBoxLayout ( )
layout_cpu . addWidget ( self . cpu_label )
layout_cpu . addWidget ( self . cpu_widget )
layout_mem = QHBoxLayout ( )
layout_mem . addWidget ( self . mem_label )
layout_mem . addWidget ( self . mem_widget )
layout = QVBoxLayout ( )
layout . addLayout ( layout_cpu )
layout . addLayout ( layout_mem )
self . setLayout ( layout )
self . update_load ( vm )
def update_load ( self , vm ) :
2011-04-02 15:27:40 +02:00
self . cpu_load = vm . get_cpu_total_load ( ) if vm . last_power_state else 0
self . mem_load = vm . get_mem ( ) / ( 1024 * 1024 ) if vm . last_power_state else 0
2011-03-07 16:48:19 +01:00
self . cpu_widget . setValue ( self . cpu_load )
self . mem_widget . setValue ( self . mem_load )
def resizeEvent ( self , Event = None ) :
label_width = max ( self . mem_label . width ( ) , self . cpu_label . width ( ) )
self . mem_label . setMinimumWidth ( label_width )
self . cpu_label . setMinimumWidth ( label_width )
super ( VmUsageWidget , self ) . resizeEvent ( Event )
2010-05-11 16:51:31 +02:00
class LoadChartWidget ( QWidget ) :
def __init__ ( self , vm , parent = None ) :
super ( LoadChartWidget , self ) . __init__ ( parent )
2011-04-02 15:27:40 +02:00
self . load = vm . get_cpu_total_load ( ) if vm . last_power_state else 0
2010-05-11 16:51:31 +02:00
assert self . load > = 0 and self . load < = 100 , " load = {0} " . format ( self . load )
self . load_history = [ self . load ]
def update_load ( self , vm ) :
2011-04-02 15:27:40 +02:00
self . load = vm . get_cpu_total_load ( ) if vm . last_power_state else 0
2010-05-11 16:51:31 +02:00
assert self . load > = 0 and self . load < = 100 , " load = {0} " . format ( self . load )
self . load_history . append ( self . load )
self . repaint ( )
def paintEvent ( self , Event = None ) :
p = QPainter ( self )
dx = 4
2011-03-07 16:48:53 +01:00
W = self . width ( )
2010-05-11 16:51:31 +02:00
H = self . height ( ) - 5
N = len ( self . load_history )
if N > W / dx :
2010-05-12 16:18:38 +02:00
tail = N - W / dx
2010-05-11 16:51:31 +02:00
N = W / dx
2010-05-12 16:18:38 +02:00
self . load_history = self . load_history [ tail : ]
assert len ( self . load_history ) == N
2010-05-11 16:51:31 +02:00
for i in range ( 0 , N - 1 ) :
val = self . load_history [ N - i - 1 ]
hue = 200
sat = 70 + val * ( 255 - 70 ) / 100
color = QColor . fromHsv ( hue , sat , 255 )
pen = QPen ( color )
pen . setWidth ( dx - 1 )
p . setPen ( pen )
if val > 0 :
p . drawLine ( W - i * dx - dx , H , W - i * dx - dx , H - ( H - 5 ) * val / 100 )
2010-09-16 18:49:36 +02:00
class MemChartWidget ( QWidget ) :
def __init__ ( self , vm , parent = None ) :
super ( MemChartWidget , self ) . __init__ ( parent )
2011-04-02 15:27:40 +02:00
self . load = vm . get_mem ( ) * 100 / qubes_host . memory_total if vm . last_power_state else 0
2010-09-16 18:49:36 +02:00
assert self . load > = 0 and self . load < = 100 , " mem = {0} " . format ( self . load )
self . load_history = [ self . load ]
def update_load ( self , vm ) :
2011-04-02 15:27:40 +02:00
self . load = vm . get_mem ( ) * 100 / qubes_host . memory_total if vm . last_power_state else 0
2010-09-16 18:49:36 +02:00
assert self . load > = 0 and self . load < = 100 , " load = {0} " . format ( self . load )
self . load_history . append ( self . load )
self . repaint ( )
def paintEvent ( self , Event = None ) :
p = QPainter ( self )
dx = 4
2011-03-07 16:48:53 +01:00
W = self . width ( )
2010-09-16 18:49:36 +02:00
H = self . height ( ) - 5
N = len ( self . load_history )
if N > W / dx :
tail = N - W / dx
N = W / dx
self . load_history = self . load_history [ tail : ]
assert len ( self . load_history ) == N
for i in range ( 0 , N - 1 ) :
val = self . load_history [ N - i - 1 ]
hue = 120
sat = 70 + val * ( 255 - 70 ) / 100
color = QColor . fromHsv ( hue , sat , 255 )
pen = QPen ( color )
pen . setWidth ( dx - 1 )
p . setPen ( pen )
if val > 0 :
p . drawLine ( W - i * dx - dx , H , W - i * dx - dx , H - ( H - 5 ) * val / 100 )
2010-05-11 16:51:31 +02:00
class VmRowInTable ( object ) :
def __init__ ( self , vm , row_no , table ) :
self . vm = vm
self . row_no = row_no
table . setRowHeight ( row_no , VmManagerWindow . row_height )
self . info_widget = VmInfoWidget ( vm )
table . setCellWidget ( row_no , 0 , self . info_widget )
2011-03-07 16:48:19 +01:00
self . usage_widget = VmUsageWidget ( vm )
table . setCellWidget ( row_no , 1 , self . usage_widget )
2010-05-11 16:51:31 +02:00
self . load_widget = LoadChartWidget ( vm )
2011-03-07 16:48:19 +01:00
table . setCellWidget ( row_no , 2 , self . load_widget )
2010-05-11 16:51:31 +02:00
2010-09-16 18:49:36 +02:00
self . mem_widget = MemChartWidget ( vm )
2011-03-07 16:48:19 +01:00
table . setCellWidget ( row_no , 3 , self . mem_widget )
2010-09-16 18:49:36 +02:00
2010-05-11 16:51:31 +02:00
def update ( self , counter ) :
self . info_widget . update_vm_state ( self . vm )
if counter % 3 == 0 :
2011-03-07 16:48:19 +01:00
self . usage_widget . update_load ( self . vm )
2010-05-11 16:51:31 +02:00
self . load_widget . update_load ( self . vm )
2010-09-16 18:49:36 +02:00
self . mem_widget . update_load ( self . vm )
2011-04-03 01:23:28 +02:00
self . info_widget . update_outdated ( self . vm )
2010-05-11 16:51:31 +02:00
class NewAppVmDlg ( QDialog , ui_newappvmdlg . Ui_NewAppVMDlg ) :
def __init__ ( self , parent = None ) :
super ( NewAppVmDlg , self ) . __init__ ( parent )
self . setupUi ( self )
vm_shutdown_timeout = 15000 # in msec
class VmShutdownMonitor ( QObject ) :
def __init__ ( self , vm ) :
self . vm = vm
def check_if_vm_has_shutdown ( self ) :
vm = self . vm
if not vm . is_running ( ) :
2011-04-03 01:24:16 +02:00
if vm . is_template ( ) :
2011-04-07 12:23:01 +02:00
trayIcon . showMessage ( " Qubes Manager " , " You have just modified template ' {0} ' . You should now restart all the VMs based on it, so they could see the changes. " . format ( vm . name ) , msecs = 8000 )
2010-05-11 16:51:31 +02:00
return
2011-03-07 16:48:53 +01:00
reply = QMessageBox . question ( None , " VM Shutdown " ,
2010-06-04 11:22:02 +02:00
" The VM <b> ' {0} ' </b> hasn ' t shutdown within the last {1} seconds, do you want to kill it?<br> " . format ( vm . name , vm_shutdown_timeout / 1000 ) ,
2010-05-11 16:51:31 +02:00
" Kill it! " , " Wait another {0} seconds... " . format ( vm_shutdown_timeout / 1000 ) )
if reply == 0 :
vm . force_shutdown ( )
else :
QTimer . singleShot ( vm_shutdown_timeout , self . check_if_vm_has_shutdown )
class ThreadMonitor ( QObject ) :
def __init__ ( self ) :
self . success = True
self . error_msg = None
self . event_finished = threading . Event ( )
def set_error_msg ( self , error_msg ) :
self . success = False
self . error_msg = error_msg
self . set_finished ( )
def is_finished ( self ) :
return self . event_finished . is_set ( )
def set_finished ( self ) :
self . event_finished . set ( )
class VmManagerWindow ( QMainWindow ) :
2011-03-07 16:48:19 +01:00
columns_widths = [ 200 , 200 , 150 , 150 ]
2010-05-11 16:51:31 +02:00
row_height = 50
max_visible_rows = 14
update_interval = 1000 # in msec
2011-03-11 19:41:46 +01:00
fw_rules_apply_check_interval = 5000
2011-04-08 20:11:31 +02:00
show_inactive_vms = True
2011-03-07 16:48:19 +01:00
columns_states = { 0 : [ 0 , 1 ] , 1 : [ 0 , 2 , 3 ] }
2010-05-11 16:51:31 +02:00
def __init__ ( self , parent = None ) :
super ( VmManagerWindow , self ) . __init__ ( parent )
self . action_createvm = self . createAction ( " Create AppVM " , slot = self . create_appvm ,
icon = " createvm " , tip = " Create a new AppVM " )
self . action_removevm = self . createAction ( " Remove AppVM " , slot = self . remove_appvm ,
icon = " removevm " , tip = " Remove an existing AppVM (must be stopped first) " )
self . action_resumevm = self . createAction ( " Start/Resume VM " , slot = self . resume_vm ,
icon = " resumevm " , tip = " Start/Resusme a VM " )
self . action_pausevm = self . createAction ( " Pause VM " , slot = self . pause_vm ,
icon = " pausevm " , tip = " Pause a running VM " )
self . action_shutdownvm = self . createAction ( " Shutdown VM " , slot = self . shutdown_vm ,
icon = " shutdownvm " , tip = " Shutdown a running VM " )
2011-03-02 12:51:29 +01:00
self . action_updatevm = self . createAction ( " Commit VM changes " , slot = self . update_vm ,
icon = " updateable " , tip = " Commit changes to template (only for ' updateable ' template VMs); VM must be stopped " )
2010-05-11 16:51:31 +02:00
2011-04-01 12:02:27 +02:00
self . action_showallvms = self . createAction ( " Show/Hide Inactive VMs " , slot = self . toggle_inactive_view , checkable = True ,
2010-05-11 16:51:31 +02:00
icon = " showallvms " , tip = " Show/Hide Inactive VMs " )
self . action_showcpuload = self . createAction ( " Show/Hide CPU Load chart " , slot = self . showcpuload , checkable = True ,
icon = " showcpuload " , tip = " Show/Hide CPU Load chart " )
2011-02-21 18:15:35 +01:00
self . action_editfwrules = self . createAction ( " Edit VM Firewall rules " , slot = self . edit_fw_rules ,
2011-03-11 15:50:07 +01:00
icon = " firewall " , tip = " Edit VM Firewall rules " )
2011-02-21 18:15:35 +01:00
2010-05-11 16:51:31 +02:00
self . action_removevm . setDisabled ( True )
self . action_resumevm . setDisabled ( True )
self . action_pausevm . setDisabled ( True )
self . action_shutdownvm . setDisabled ( True )
self . action_updatevm . setDisabled ( True )
self . toolbar = self . addToolBar ( " Toolbar " )
self . toolbar . setFloatable ( False )
self . addActions ( self . toolbar , ( self . action_createvm , self . action_removevm ,
None ,
2011-04-01 12:20:47 +02:00
self . action_resumevm , self . action_shutdownvm ,
2011-04-07 10:45:21 +02:00
self . action_editfwrules ,
2010-05-11 16:51:31 +02:00
None ,
self . action_showcpuload ,
2011-04-01 12:02:27 +02:00
self . action_showallvms ,
2010-05-11 16:51:31 +02:00
) )
2011-03-07 16:48:53 +01:00
2010-05-11 16:51:31 +02:00
self . table = QTableWidget ( )
self . setCentralWidget ( self . table )
self . table . clear ( )
self . table . setColumnCount ( len ( VmManagerWindow . columns_widths ) )
for ( col , width ) in enumerate ( VmManagerWindow . columns_widths ) :
self . table . setColumnWidth ( col , width )
2011-03-07 16:48:19 +01:00
self . table . horizontalHeader ( ) . setResizeMode ( QHeaderView . Stretch )
self . table . horizontalHeader ( ) . setResizeMode ( 0 , QHeaderView . Fixed )
2010-05-11 16:51:31 +02:00
self . table . setAlternatingRowColors ( True )
self . table . verticalHeader ( ) . hide ( )
self . table . horizontalHeader ( ) . hide ( )
self . table . setGridStyle ( Qt . NoPen )
self . table . setSortingEnabled ( False )
self . table . setSelectionBehavior ( QTableWidget . SelectRows )
self . table . setSelectionMode ( QTableWidget . SingleSelection )
2011-03-07 16:48:19 +01:00
self . __cpugraphs = self . action_showcpuload . isChecked ( )
self . update_table_columns ( )
2010-05-11 16:51:31 +02:00
self . qvm_collection = QubesVmCollection ( )
self . setWindowTitle ( " Qubes VM Manager " )
2011-03-07 16:48:19 +01:00
2010-05-11 16:51:31 +02:00
self . connect ( self . table , SIGNAL ( " itemSelectionChanged() " ) , self . table_selection_changed )
2011-04-06 23:45:23 +02:00
cur_pos = self . pos ( )
2011-04-02 15:48:48 +02:00
self . setFixedWidth ( self . get_minimum_table_width ( ) )
2010-05-11 16:51:31 +02:00
self . fill_table ( )
2011-04-06 23:45:23 +02:00
self . move ( cur_pos )
2010-05-11 16:51:31 +02:00
2011-04-02 15:48:48 +02:00
self . counter = 0
self . shutdown_monitor = { }
QTimer . singleShot ( self . update_interval , self . update_table )
QTimer . singleShot ( self . fw_rules_apply_check_interval , self . check_apply_fw_rules )
2010-05-11 16:51:31 +02:00
2011-04-02 15:48:48 +02:00
def set_table_geom_height ( self ) :
2010-05-11 16:51:31 +02:00
# TODO: '6' -- WTF?!
tbl_H = self . toolbar . height ( ) + 6 + \
self . table . horizontalHeader ( ) . height ( ) + 6
n = self . table . rowCount ( ) ;
if n > VmManagerWindow . max_visible_rows :
n = VmManagerWindow . max_visible_rows
for i in range ( 0 , n ) :
tbl_H + = self . table . rowHeight ( i )
2011-04-02 15:48:48 +02:00
self . setFixedHeight ( tbl_H )
2010-05-11 16:51:31 +02:00
def addActions ( self , target , actions ) :
for action in actions :
if action is None :
target . addSeparator ( )
else :
target . addAction ( action )
def createAction ( self , text , slot = None , shortcut = None , icon = None ,
tip = None , checkable = False , signal = " triggered() " ) :
action = QAction ( text , self )
if icon is not None :
action . setIcon ( QIcon ( " :/ %s .png " % icon ) )
if shortcut is not None :
action . setShortcut ( shortcut )
if tip is not None :
action . setToolTip ( tip )
action . setStatusTip ( tip )
if slot is not None :
self . connect ( action , SIGNAL ( signal ) , slot )
if checkable :
action . setCheckable ( True )
return action
def get_vms_list ( self ) :
self . qvm_collection . lock_db_for_reading ( )
self . qvm_collection . load ( )
self . qvm_collection . unlock_db ( )
2011-04-02 15:27:40 +02:00
vms_list = [ vm for vm in self . qvm_collection . values ( ) ]
for vm in vms_list :
vm . last_power_state = vm . is_running ( )
2010-05-11 16:51:31 +02:00
no_vms = len ( vms_list )
vms_to_display = [ ]
# First, the NetVMs...
for netvm in vms_list :
if netvm . is_netvm ( ) :
vms_to_display . append ( netvm )
# Now, the templates...
for tvm in vms_list :
2011-03-05 15:12:34 +01:00
if tvm . is_template ( ) :
2010-05-11 16:51:31 +02:00
vms_to_display . append ( tvm )
label_list = QubesVmLabels . values ( )
label_list . sort ( key = lambda l : l . index )
for label in [ label . name for label in label_list ] :
2010-09-23 01:18:26 +02:00
for appvm in [ vm for vm in vms_list if ( ( vm . is_appvm ( ) or vm . is_disposablevm ( ) ) and vm . label . name == label ) ] :
2010-05-11 16:51:31 +02:00
vms_to_display . append ( appvm )
assert len ( vms_to_display ) == no_vms
return vms_to_display
def fill_table ( self ) :
self . table . clear ( )
vms_list = self . get_vms_list ( )
self . table . setRowCount ( len ( vms_list ) )
vms_in_table = [ ]
2011-04-02 15:27:40 +02:00
row_no = 0
for vm in vms_list :
if ( not self . show_inactive_vms ) and ( not vm . last_power_state ) :
continue
2011-04-04 12:02:12 +02:00
if vm . internal :
continue
2010-05-11 16:51:31 +02:00
vm_row = VmRowInTable ( vm , row_no , self . table )
vms_in_table . append ( vm_row )
2011-04-02 15:27:40 +02:00
row_no + = 1
2010-05-11 16:51:31 +02:00
2011-04-02 15:27:40 +02:00
self . table . setRowCount ( row_no )
2011-04-02 15:48:48 +02:00
self . set_table_geom_height ( )
2010-05-11 16:51:31 +02:00
self . vms_list = vms_list
self . vms_in_table = vms_in_table
self . reload_table = False
def mark_table_for_update ( self ) :
self . reload_table = True
# When calling update_table() directly, always use out_of_schedule=True!
def update_table ( self , out_of_schedule = False ) :
2011-04-02 16:27:18 +02:00
if manager_window . isVisible ( ) :
some_vms_have_changed_power_state = False
for vm in self . vms_list :
state = vm . is_running ( ) ;
if vm . last_power_state != state :
vm . last_power_state = state
some_vms_have_changed_power_state = True
2010-05-11 16:51:31 +02:00
2011-04-02 16:27:18 +02:00
if self . reload_table or ( ( not self . show_inactive_vms ) and some_vms_have_changed_power_state ) :
self . fill_table ( )
2010-05-11 16:51:31 +02:00
2011-04-02 16:27:18 +02:00
for vm_row in self . vms_in_table :
vm_row . update ( self . counter )
2010-05-11 16:51:31 +02:00
2011-04-02 16:27:18 +02:00
self . table_selection_changed ( )
2010-05-11 16:51:31 +02:00
if not out_of_schedule :
self . counter + = 1
QTimer . singleShot ( self . update_interval , self . update_table )
2011-03-07 16:48:19 +01:00
def update_table_columns ( self ) :
state = 1 if self . __cpugraphs else 0
columns = self . columns_states [ state ]
for i in range ( 0 , self . table . columnCount ( ) ) :
enabled = columns . count ( i ) > 0
self . table . setColumnHidden ( i , not enabled )
self . setMinimumWidth ( self . get_minimum_table_width ( ) )
2010-05-11 16:51:31 +02:00
def table_selection_changed ( self ) :
vm = self . get_selected_vm ( )
# Update available actions:
2011-04-02 15:27:40 +02:00
self . action_removevm . setEnabled ( not vm . installed_by_rpm and not vm . last_power_state )
self . action_resumevm . setEnabled ( not vm . last_power_state )
self . action_pausevm . setEnabled ( vm . last_power_state and vm . qid != 0 )
2011-04-07 10:46:00 +02:00
self . action_shutdownvm . setEnabled ( not vm . is_netvm ( ) and vm . last_power_state and vm . qid != 0 )
2011-04-02 15:27:40 +02:00
self . action_updatevm . setEnabled ( vm . is_updateable ( ) and not vm . last_power_state )
2011-03-31 23:59:37 +02:00
self . action_editfwrules . setEnabled ( vm . is_networked ( ) and not ( vm . is_netvm ( ) and not vm . is_proxyvm ( ) ) )
2010-05-11 16:51:31 +02:00
2011-03-07 16:48:19 +01:00
def get_minimum_table_width ( self ) :
tbl_W = 0
for ( col , w ) in enumerate ( VmManagerWindow . columns_widths ) :
if not self . table . isColumnHidden ( col ) :
tbl_W + = w
return tbl_W
2010-05-11 16:51:31 +02:00
def closeEvent ( self , event ) :
self . hide ( )
event . ignore ( )
def create_appvm ( self ) :
dialog = NewAppVmDlg ( )
# Theoretically we should be locking for writing here and unlock
# only after the VM creation finished. But the code would be more messy...
# Instead we lock for writing in the actual worker thread
self . qvm_collection . lock_db_for_reading ( )
self . qvm_collection . load ( )
self . qvm_collection . unlock_db ( )
label_list = QubesVmLabels . values ( )
label_list . sort ( key = lambda l : l . index )
for ( i , label ) in enumerate ( label_list ) :
dialog . vmlabel . insertItem ( i , label . name )
dialog . vmlabel . setItemIcon ( i , QIcon ( label . icon_path ) )
2011-04-03 17:50:32 +02:00
template_vm_list = [ vm for vm in self . qvm_collection . values ( ) if not vm . internal and vm . is_template ( ) ]
2010-05-11 16:51:31 +02:00
default_index = 0
for ( i , vm ) in enumerate ( template_vm_list ) :
if vm is self . qvm_collection . get_default_template_vm ( ) :
default_index = i
dialog . template_name . insertItem ( i , vm . name + " (default) " )
else :
dialog . template_name . insertItem ( i , vm . name )
dialog . template_name . setCurrentIndex ( default_index )
dialog . vmname . selectAll ( )
dialog . vmname . setFocus ( )
if dialog . exec_ ( ) :
vmname = str ( dialog . vmname . text ( ) )
if self . qvm_collection . get_vm_by_name ( vmname ) is not None :
QMessageBox . warning ( None , " Incorrect AppVM Name! " , " A VM with the name <b> {0} </b> already exists in the system! " . format ( vmname ) )
return
label = label_list [ dialog . vmlabel . currentIndex ( ) ]
template_vm = template_vm_list [ dialog . template_name . currentIndex ( ) ]
2011-03-14 21:37:08 +01:00
allow_networking = dialog . allow_networking . isChecked ( )
2010-05-11 16:51:31 +02:00
thread_monitor = ThreadMonitor ( )
2011-03-14 21:37:08 +01:00
thread = threading . Thread ( target = self . do_create_appvm , args = ( vmname , label , template_vm , allow_networking , thread_monitor ) )
2010-05-11 16:51:31 +02:00
thread . daemon = True
thread . start ( )
progress = QProgressDialog ( " Creating new AppVM <b> {0} </b>... " . format ( vmname ) , " " , 0 , 0 )
progress . setCancelButton ( None )
progress . setModal ( True )
progress . show ( )
2011-03-07 16:48:53 +01:00
2010-05-11 16:51:31 +02:00
while not thread_monitor . is_finished ( ) :
app . processEvents ( )
time . sleep ( 0.1 )
progress . hide ( )
if thread_monitor . success :
trayIcon . showMessage ( " Qubes Manager " , " VM ' {0} ' has been created. " . format ( vmname ) , msecs = 3000 )
else :
QMessageBox . warning ( None , " Error creating AppVM! " , " ERROR: {0} " . format ( thread_monitor . error_msg ) )
2011-03-14 21:37:08 +01:00
def do_create_appvm ( self , vmname , label , template_vm , allow_networking , thread_monitor ) :
2011-03-09 15:26:27 +01:00
vm = None
2010-05-11 16:51:31 +02:00
try :
self . qvm_collection . lock_db_for_writing ( )
self . qvm_collection . load ( )
vm = self . qvm_collection . add_new_appvm ( vmname , template_vm , label = label )
vm . create_on_disk ( verbose = False )
vm . add_to_xen_storage ( )
2011-03-14 21:37:08 +01:00
firewall = vm . get_firewall_conf ( )
firewall [ " allow " ] = allow_networking
firewall [ " allowDns " ] = allow_networking
vm . write_firewall_conf ( firewall )
2010-05-11 16:51:31 +02:00
self . qvm_collection . save ( )
except Exception as ex :
thread_monitor . set_error_msg ( str ( ex ) )
2011-03-09 15:26:27 +01:00
if vm :
vm . remove_from_disk ( )
2010-05-11 16:51:31 +02:00
finally :
self . qvm_collection . unlock_db ( )
thread_monitor . set_finished ( )
def get_selected_vm ( self ) :
row_index = self . table . currentRow ( )
assert self . vms_in_table [ row_index ] is not None
vm = self . vms_in_table [ row_index ] . vm
return vm
def remove_appvm ( self ) :
vm = self . get_selected_vm ( )
assert not vm . is_running ( )
assert not vm . installed_by_rpm
self . qvm_collection . lock_db_for_reading ( )
self . qvm_collection . load ( )
self . qvm_collection . unlock_db ( )
2011-03-05 15:12:34 +01:00
if vm . is_template ( ) :
2010-05-11 16:51:31 +02:00
dependent_vms = self . qvm_collection . get_vms_based_on ( vm . qid )
if len ( dependent_vms ) > 0 :
2011-03-07 16:48:53 +01:00
QMessageBox . warning ( None , " Warning! " ,
2010-05-11 16:51:31 +02:00
" This Template VM cannot be removed, because there is at least one AppVM that is based on it.<br> "
" <small>If you want to remove this Template VM and all the AppVMs based on it, "
" you should first remove each individual AppVM that uses this template.</small> " )
return
2011-03-07 16:48:53 +01:00
reply = QMessageBox . question ( None , " VM Removal Confirmation " ,
2010-05-11 16:51:31 +02:00
" Are you sure you want to remove the VM <b> ' {0} ' </b>?<br> "
" <small>All data on this VM ' s private storage will be lost!</small> " . format ( vm . name ) ,
QMessageBox . Yes | QMessageBox . Cancel )
if reply == QMessageBox . Yes :
thread_monitor = ThreadMonitor ( )
thread = threading . Thread ( target = self . do_remove_vm , args = ( vm , thread_monitor ) )
thread . daemon = True
thread . start ( )
progress = QProgressDialog ( " Removing VM: <b> {0} </b>... " . format ( vm . name ) , " " , 0 , 0 )
progress . setCancelButton ( None )
progress . setModal ( True )
progress . show ( )
2011-03-07 16:48:53 +01:00
2010-05-11 16:51:31 +02:00
while not thread_monitor . is_finished ( ) :
app . processEvents ( )
time . sleep ( 0.1 )
progress . hide ( )
if thread_monitor . success :
trayIcon . showMessage ( " Qubes Manager " , " VM ' {0} ' has been removed. " . format ( vm . name ) , msecs = 3000 )
else :
2011-03-09 17:52:04 +01:00
QMessageBox . warning ( None , " Error removing VM! " , " ERROR: {0} " . format ( thread_monitor . error_msg ) )
2010-05-11 16:51:31 +02:00
def do_remove_vm ( self , vm , thread_monitor ) :
try :
self . qvm_collection . lock_db_for_writing ( )
self . qvm_collection . load ( )
#TODO: the following two conditions should really be checked by qvm_collection.pop() overload...
2011-03-05 15:12:34 +01:00
if vm . is_template ( ) and qvm_collection . default_template_qid == vm . qid :
2010-05-11 16:51:31 +02:00
qvm_collection . default_template_qid = None
if vm . is_netvm ( ) and qvm_collection . default_netvm_qid == vm . qid :
qvm_collection . default_netvm_qid = None
vm . remove_from_xen_storage ( )
vm . remove_from_disk ( )
self . qvm_collection . pop ( vm . qid )
self . qvm_collection . save ( )
except Exception as ex :
thread_monitor . set_error_msg ( str ( ex ) )
finally :
self . qvm_collection . unlock_db ( )
thread_monitor . set_finished ( )
def resume_vm ( self ) :
2011-03-02 14:42:11 +01:00
vm = self . get_selected_vm ( )
assert not vm . is_running ( )
2011-03-27 17:41:03 +02:00
if vm . is_paused ( ) :
try :
subprocess . check_call ( [ " /usr/sbin/xm " , " unpause " , vm . name ] )
except Exception as ex :
QMessageBox . warning ( None , " Error unpausing VM! " , " ERROR: {0} " . format ( ex ) )
return
2011-04-01 00:46:51 +02:00
thread_monitor = ThreadMonitor ( )
thread = threading . Thread ( target = self . do_start_vm , args = ( vm , thread_monitor ) )
thread . daemon = True
thread . start ( )
trayIcon . showMessage ( " Qubes Manager " , " Starting ' {0} ' ... " . format ( vm . name ) , msecs = 3000 )
while not thread_monitor . is_finished ( ) :
app . processEvents ( )
time . sleep ( 0.1 )
if thread_monitor . success :
trayIcon . showMessage ( " Qubes Manager " , " VM ' {0} ' has been started. " . format ( vm . name ) , msecs = 3000 )
else :
QMessageBox . warning ( None , " Error starting VM! " , " ERROR: {0} " . format ( thread_monitor . error_msg ) )
def do_start_vm ( self , vm , thread_monitor ) :
2011-03-02 14:42:11 +01:00
try :
vm . verify_files ( )
xid = vm . start ( )
2011-04-01 00:46:51 +02:00
except Exception as ex :
thread_monitor . set_error_msg ( str ( ex ) )
thread_monitor . set_finished ( )
2011-03-02 14:42:11 +01:00
return
2011-03-07 16:48:53 +01:00
2011-03-02 14:42:11 +01:00
retcode = subprocess . call ( [ qubes_guid_path , " -d " , str ( xid ) , " -c " , vm . label . color , " -i " , vm . label . icon , " -l " , str ( vm . label . index ) ] )
if ( retcode != 0 ) :
2011-04-03 02:26:23 +02:00
thread_monitor . set_error_msg ( " Cannot start qubes_guid! " )
2011-04-01 00:46:51 +02:00
thread_monitor . set_finished ( )
2010-05-11 16:51:31 +02:00
def pause_vm ( self ) :
2011-03-02 14:50:21 +01:00
vm = self . get_selected_vm ( )
assert vm . is_running ( )
try :
subprocess . check_call ( [ " /usr/sbin/xm " , " pause " , vm . name ] )
except Exception as ex :
QMessageBox . warning ( None , " Error pausing VM! " , " ERROR: {0} " . format ( ex ) )
return
2010-05-11 16:51:31 +02:00
def shutdown_vm ( self ) :
vm = self . get_selected_vm ( )
assert vm . is_running ( )
2011-03-07 16:48:53 +01:00
reply = QMessageBox . question ( None , " VM Shutdown Confirmation " ,
2010-05-11 16:51:31 +02:00
" Are you sure you want to power down the VM <b> ' {0} ' </b>?<br> "
" <small>This will shutdown all the running applications within this VM.</small> " . format ( vm . name ) ,
QMessageBox . Yes | QMessageBox . Cancel )
2010-05-12 16:44:48 +02:00
app . processEvents ( )
2010-05-11 16:51:31 +02:00
if reply == QMessageBox . Yes :
try :
subprocess . check_call ( [ " /usr/sbin/xm " , " shutdown " , vm . name ] )
except Exception as ex :
QMessageBox . warning ( None , " Error shutting down VM! " , " ERROR: {0} " . format ( ex ) )
return
trayIcon . showMessage ( " Qubes Manager " , " VM ' {0} ' is shutting down... " . format ( vm . name ) , msecs = 3000 )
self . shutdown_monitor [ vm . qid ] = VmShutdownMonitor ( vm )
QTimer . singleShot ( vm_shutdown_timeout , self . shutdown_monitor [ vm . qid ] . check_if_vm_has_shutdown )
2011-03-02 12:51:29 +01:00
def update_vm ( self ) :
vm = self . get_selected_vm ( )
2011-03-13 18:43:55 +01:00
assert not vm . is_running ( )
2011-03-02 12:51:29 +01:00
2011-03-13 18:43:55 +01:00
reply = QMessageBox . question ( None , " VM Update Confirmation " ,
" Are you sure you want to commit template <b> ' {0} ' </b> changes?<br> "
" <small>AppVMs will see the changes after restart.</small> " . format ( vm . name ) ,
QMessageBox . Yes | QMessageBox . Cancel )
app . processEvents ( )
if reply == QMessageBox . Yes :
try :
vm . commit_changes ( ) ;
except Exception as ex :
QMessageBox . warning ( None , " Error commiting changes! " , " ERROR: {0} " . format ( ex ) )
return
2011-03-13 18:46:27 +01:00
trayIcon . showMessage ( " Qubes Manager " , " Changes to template ' {0} ' commited. " . format ( vm . name ) , msecs = 3000 )
2011-03-02 12:51:29 +01:00
2010-05-11 16:51:31 +02:00
def showcpuload ( self ) :
2011-03-07 16:48:19 +01:00
self . __cpugraphs = self . action_showcpuload . isChecked ( )
self . update_table_columns ( )
2010-05-11 16:51:31 +02:00
2011-04-01 12:02:27 +02:00
def toggle_inactive_view ( self ) :
2011-04-02 15:27:40 +02:00
self . show_inactive_vms = self . action_showallvms . isChecked ( )
self . mark_table_for_update ( )
self . update_table ( out_of_schedule = True )
2011-04-01 12:02:27 +02:00
2011-02-21 18:15:35 +01:00
def edit_fw_rules ( self ) :
vm = self . get_selected_vm ( )
dialog = EditFwRulesDlg ( )
model = QubesFirewallRulesModel ( )
model . set_vm ( vm )
dialog . set_model ( model )
2011-03-21 22:36:00 +01:00
if vm . netvm_vm is not None and not vm . netvm_vm . is_proxyvm ( ) :
QMessageBox . warning ( None , " VM configuration problem! " , " The ' {0} ' AppVM is not network connected to a FirewallVM!<p> " . format ( vm . name ) + \
" You may edit the ' {0} ' VM firewall rules, but these will not take any effect until you connect it to a working Firewall VM. " . format ( vm . name ) )
2011-02-21 18:15:35 +01:00
if dialog . exec_ ( ) :
model . apply_rules ( )
2011-03-09 17:52:32 +01:00
def check_apply_fw_rules ( self ) :
qvm_collection = QubesVmCollection ( )
qvm_collection . lock_db_for_reading ( )
qvm_collection . load ( )
qvm_collection . unlock_db ( )
for vm in qvm_collection . values ( ) :
2011-03-27 17:19:56 +02:00
if vm . is_proxyvm ( ) and vm . is_running ( ) :
2011-03-09 17:52:32 +01:00
error_file = " /local/domain/ {0} /qubes_iptables_error " . format ( vm . get_xid ( ) )
error = subprocess . Popen (
[ " /usr/bin/xenstore-read " , error_file ] ,
stdout = subprocess . PIPE ) . communicate ( ) [ 0 ]
2011-03-11 19:41:46 +01:00
error = error . strip ( " \n \t " )
2011-03-09 17:52:32 +01:00
if error != " " :
2011-03-09 18:07:32 +01:00
vm . rules_applied = False
2011-03-09 17:52:32 +01:00
trayIcon . showMessage (
" Error applying firewall rules on ' {0} ' ! " . format ( vm . name ) ,
" ERROR: {0} " . format ( error . decode ( ' string_escape ' ) ) ,
QSystemTrayIcon . Critical
)
retcode = subprocess . check_call (
[ " /usr/bin/xenstore-write " , error_file , " " ] )
2011-03-09 18:07:32 +01:00
else :
vm . rules_applied = True
2011-03-07 16:48:53 +01:00
2011-03-11 19:41:46 +01:00
QTimer . singleShot ( self . fw_rules_apply_check_interval , self . check_apply_fw_rules )
2010-05-11 16:51:31 +02:00
class QubesTrayIcon ( QSystemTrayIcon ) :
def __init__ ( self , icon ) :
QSystemTrayIcon . __init__ ( self , icon )
self . menu = QMenu ( )
action_showmanager = self . createAction ( " Open VM Manager " , slot = show_manager , icon = " qubes " )
action_backup = self . createAction ( " Make backup " )
action_preferences = self . createAction ( " Preferences " )
action_set_netvm = self . createAction ( " Set default NetVM " , icon = " networking " )
action_sys_info = self . createAction ( " System Info " , icon = " dom0 " )
action_exit = self . createAction ( " Exit " , slot = exit_app )
action_backup . setDisabled ( True )
action_preferences . setDisabled ( True )
action_set_netvm . setDisabled ( True )
action_sys_info . setDisabled ( True )
self . addActions ( self . menu , ( action_showmanager , action_backup , action_sys_info , None , action_preferences , action_set_netvm , None , action_exit ) )
self . setContextMenu ( self . menu )
self . connect ( self , SIGNAL ( " activated (QSystemTrayIcon::ActivationReason) " ) , self . icon_clicked )
def icon_clicked ( self , reason ) :
if reason == QSystemTrayIcon . Context :
# Handle the right click normally, i.e. display the context menu
return
else :
2011-03-10 18:09:52 +01:00
toggle_manager ( )
2010-05-11 16:51:31 +02:00
def addActions ( self , target , actions ) :
for action in actions :
if action is None :
target . addSeparator ( )
else :
target . addAction ( action )
def createAction ( self , text , slot = None , shortcut = None , icon = None ,
tip = None , checkable = False , signal = " triggered() " ) :
action = QAction ( text , self )
if icon is not None :
action . setIcon ( QIcon ( " :/ %s .png " % icon ) )
if shortcut is not None :
action . setShortcut ( shortcut )
if tip is not None :
action . setToolTip ( tip )
action . setStatusTip ( tip )
if slot is not None :
self . connect ( action , SIGNAL ( signal ) , slot )
if checkable :
action . setCheckable ( True )
return action
def show_manager ( ) :
manager_window . show ( )
2011-03-10 18:09:52 +01:00
def toggle_manager ( ) :
if manager_window . isVisible ( ) :
manager_window . hide ( )
else :
manager_window . show ( )
2010-05-11 16:51:31 +02:00
def exit_app ( ) :
notifier . stop ( )
app . exit ( )
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
def handle_exception ( exc_type , exc_value , exc_traceback ) :
import sys
import os . path
import traceback
filename , line , dummy , dummy = traceback . extract_tb ( exc_traceback ) . pop ( )
filename = os . path . basename ( filename )
error = " %s : %s " % ( exc_type . __name__ , exc_value )
QMessageBox . critical ( None , " Houston, we have a problem... " ,
" Whoops. A critical error has occured. This is most likely a bug "
" in Qubes Manager.<br><br> "
" <b><i> %s </i></b> " % error +
" at <b>line %d </b> of file <b> %s </b>.<br/><br/> "
% ( line , filename ) )
#sys.exit(1)
def main ( ) :
# Avoid starting more than one instance of the app
lock = QubesDaemonPidfile ( " qubes-manager " )
if lock . pidfile_exists ( ) :
if lock . pidfile_is_stale ( ) :
lock . remove_pidfile ( )
print " Removed stale pidfile (has the previous daemon instance crashed?). "
else :
exit ( 0 )
lock . create_pidfile ( )
2010-09-16 18:49:36 +02:00
global qubes_host
qubes_host = QubesHost ( )
2010-05-11 16:51:31 +02:00
global app
app = QApplication ( sys . argv )
app . setOrganizationName ( " The Qubes Project " )
app . setOrganizationDomain ( " http://qubes-os.org " )
app . setApplicationName ( " Qubes VM Manager " )
app . setWindowIcon ( QIcon ( " :/qubes.png " ) )
sys . excepthook = handle_exception
global manager_window
manager_window = VmManagerWindow ( )
wm = WatchManager ( )
2011-03-14 22:17:28 +01:00
mask = EventsCodes . OP_FLAGS . get ( ' IN_MODIFY ' )
2010-05-11 16:51:31 +02:00
global notifier
notifier = ThreadedNotifier ( wm , QubesConfigFileWatcher ( manager_window . mark_table_for_update ) )
notifier . start ( )
wdd = wm . add_watch ( qubes_store_filename , mask )
global trayIcon
trayIcon = QubesTrayIcon ( QIcon ( " :/qubes.png " ) )
trayIcon . show ( )
app . exec_ ( )
trayIcon = None