root/projects/AsynCluster/trunk/asyncluster/ndm/gui.py

Revision 127, 9.5 kB (checked in by edsuom, 10 months ago)

Fixed NDM niceness setting; headless node option; setting for number of jobs queued per worker at a time

Line 
1 # AsynCluster: Node Display Manager (NDM)
2 # A simple X display manager for cluster nodes that also serve as
3 # access-restricted workstations.
4 #
5 # An NDM client runs on each node and communicates via Twisted's Perspective
6 # Broker to the Aysncluster server, which regulates when and how much each user
7 # can use his account on any of the workstations. The NDM server also
8 # dispatches cluster operations to the nodes via the NDM clients, unbeknownst
9 # to the workstation users.
10 #
11 # Copyright (C) 2006-2007 by Edwin A. Suominen, http://www.eepatents.com
12 #
13 # This program is free software; you can redistribute it and/or modify it under
14 # the terms of the GNU General Public License as published by the Free Software
15 # Foundation; either version 2 of the License, or (at your option) any later
16 # version.
17 #
18 # This program is distributed in the hope that it will be useful, but WITHOUT
19 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 # FOR A PARTICULAR PURPOSE.  See the file COPYING for more details.
21 #
22 # You should have received a copy of the GNU General Public License along with
23 # this program; if not, write to the Free Software Foundation, Inc., 51
24 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25
26 """
27 GUI operation of non-headless NDM application.
28
29 Installs a PyQt4 QApplication() object into Twisted's qtreactor().
30 """
31
32 # Start PyQt4 with Twisted integration
33 from twisted_goodies.qtwisted import qt4reactor
34 from PyQt4.QtGui import QApplication
35 app = QApplication([])
36 qt4reactor.install(app)
37
38 # Now the regular imports
39 import os, pwd
40 from twisted.internet import defer, reactor, protocol
41 from PyQt4 import QtCore, QtGui
42
43 from asyncluster import util
44
45
46 class LoginWindow(QtGui.QWidget):
47     """
48     I am the senior GUI manager for the NDM application, acting as the main
49     window for the QApplication object.
50     """
51     def __init__(self, main):
52         QtGui.QWidget.__init__(self)
53         self.main = main
54         # Window setup, then widget setup
55         self.setupWindow()
56         self.setupWidgets()
57         self.show()
58    
59     def setupWindow(self):
60         """
61         Top-level B{window} setup
62         """
63         # Fixed Size and centered (initial) position
64         size = [int(x) for x in self.main.config['display']['size']]
65         center = [getattr(app.desktop().size(), x)()/2
66                   for x in ('width', 'height')]
67         rect = QtCore.QRect()
68         rect.setWidth(size[0]); rect.setHeight(size[1])
69         rect.moveCenter(QtCore.QPoint(*center))
70         self.setGeometry(rect)
71         self.setFixedSize(*size)
72
73     def setupWidgets(self):
74         """
75         Top-level B{widget} setup.
76         """
77         def policy(widget, *policyNames):
78             policies = [getattr(QtGui.QSizePolicy, x) for x in policyNames]
79             widget.setSizePolicy(*policies)
80        
81         self.layout = QtGui.QGridLayout(self)
82         # Labels
83         self.labels = []
84         labelSpecs = (
85             ("Node Display Manager - User Login", -1, QtCore.Qt.AlignCenter),
86             ("User ID:", 1, QtCore.Qt.AlignRight),
87             ("Password:", 1, QtCore.Qt.AlignRight))
88         for labelText, colspan, alignment in labelSpecs:
89             row = len(self.labels)
90             label = QtGui.QLabel(self.tr(labelText))
91             policy(label, 'Minimum', 'Fixed')
92             self.layout.addWidget(label, row, 0, 1, colspan, alignment)
93             self.labels.append(label)
94         # Text entry boxes for login
95         entrySpecs = (
96             ("user", QtGui.QLineEdit.Normal, 1),
97             ("password", QtGui.QLineEdit.Password, 2))
98         for lineEditorName, echoMode, row in entrySpecs:
99             lineEditor = CustomLineEditor()
100             lineEditor.setEchoMode(echoMode)
101             policy(lineEditor, 'Minimum', 'Fixed')
102             self.layout.addWidget(lineEditor, row, 1, 1, 3)
103             QtCore.QObject.connect(
104                 lineEditor, QtCore.SIGNAL("returnPressed()"), self.login)
105             setattr(self, lineEditorName, lineEditor)
106         self.user.setFocus()
107         # That's all         
108
109     def login(self):
110         """
111         Attempt a user login with the text in the I{user} and I{password} line
112         editors, disabling further logins until the session is over.
113         """
114         login = [str(getattr(self, attrName).text())
115                  for attrName in ('user', 'password')]
116         self.password.clear()
117         self.main.sessionBegin(*login)
118
119
120 class SessionWindow(QtGui.QWidget):
121     """
122     I am a standalone window that manages the active session and displays its
123     status.
124     """
125     def __init__(self, main, user):
126         QtGui.QWidget.__init__(self)
127         self.main, self.user = main, user
128         self.setup()
129         del self.minutesLeft
130         self.wmStart()
131
132     def _getML(self):
133         return self.progressBar.value()
134     def _setML(self, minutes):
135         if not self._setAlready:
136             self._setAlready = True
137             self.progressBar.setMaximum(minutes)
138         self.progressBar.setValue(minutes)
139     def _delML(self):
140         self._setAlready = False
141     minutesLeft = property(_getML, _setML, _delML)
142
143     def setup(self):
144         """
145         Window and widget setup.
146         """
147         def sp(*policyNames):
148             policies = [
149                 getattr(QtGui.QSizePolicy, x) for x in policyNames]
150             w.setSizePolicy(*policies)
151        
152         self.setWindowTitle(self.tr("NDM Session"))
153         layout = QtGui.QGridLayout(self)
154         # Fixed top label
155         text = self.tr("User Session for <b>%s</b>" % self.user)
156         w = self.topLabel = QtGui.QLabel(text)
157         util.biggerFont(w, 2.0)
158         sp('MinimumExpanding', 'Fixed')
159         layout.addWidget(w, 0, 0, 1, 3)
160         # Status display label
161         w = self.statusLabel = QtGui.QLabel()
162         sp('MinimumExpanding', 'Fixed')
163         layout.addWidget(w, 1, 0, 1, 3)
164         # Progress bar for showing time left
165         w = self.progressBar = QtGui.QProgressBar()
166         sp('MinimumExpanding', 'Fixed')
167         layout.addWidget(w, 2, 0, 1, 3)
168         # Logout button
169         # DISABLED - login box doesn't reappear
170         # w = self.quitButton = QtGui.QPushButton(self.tr("&Logout"))
171         # sp('Fixed', 'Fixed')
172         # QtCore.QObject.connect(
173         #    w, QtCore.SIGNAL("clicked()"), self.close)
174         # layout.addWidget(w, 3, 1, 1, 1)
175
176     def update(self, hoursLeft):
177         """
178         Call this method to updates the status label and progress bar in
179         accordance with the number of hours left, and to end the session when
180         the time's up.
181         """
182         hrs, minutes = divmod(int(60*hoursLeft), 60)
183         msg = "Remaining: %d:%02d" % (hrs, minutes)
184         minutesLeft = 60*hrs + minutes
185         if minutesLeft < 10:
186             msg += " !!!"
187         self.status(msg)
188         self.minutesLeft = minutesLeft
189
190     def status(self, msg):
191         """
192         Updates the status label with the supplied I{msg}.
193         """
194         if msg.endswith("!"):
195             msg = "<b>%s</b>" % msg
196             self.activateWindow()
197         self.statusLabel.setText(self.tr(msg))
198
199     def wmStart(self):
200         """
201         Spawns a process for the window manager such that the session is ended
202         when the process ends, or vice versa.
203         
204         Adapted from L{twisted.internet.util}.
205         """
206         p = WindowManagerProcessProtocol()
207         niceness = int(self.main.config['display']['niceness'])
208         windowManager = self.main.config['display']['window manager']
209         homeDir = os.path.expanduser("~%s" % self.user)
210         env = {'USER':      self.user,
211                'LOGNAME':   self.user,
212                'HOME':      homeDir}
213         for varName in ('DISPLAY', 'PATH', 'TERM',
214                         'SHELL', 'LANG', 'LANGUAGE', 'PS1'):
215             if varName in os.environ:
216                 env[varName] = os.environ[varName]
217         uid = pwd.getpwnam(self.user)[2]
218         self.process = reactor.spawnProcess(
219             p, windowManager, (windowManager,),
220             env=env, path=homeDir, uid=uid)
221         os.system("renice +%d -u %s" % (niceness, self.user))
222         p.d.addCallback(lambda _: self.sessionEnd())
223    
224     def sessionEnd(self):
225         def ended(null):
226             os.system("killall --user %s" % self.user)
227        
228         return self.main.sessionEnd().addCallback(ended)
229    
230     def wmStop(self):
231         """
232         Kills the window manager process, if it's running.
233         """
234         if hasattr(self, 'process'):
235             self.process.loseConnection()
236             del self.process
237             os.system("killall --user %s" % self.user)
238             util.log("Killed all user processes")
239    
240     def closeEvent(self, event):
241         """
242         Called when a session window has been closed.
243         """
244         self.sessionEnd()
245
246
247 class CustomLineEditor(QtGui.QLineEdit):
248     """
249     Custom line editor to ward off various hacks of login entry boxes.
250     """
251     def __init__(self):
252         QtGui.QLineEdit.__init__(self)
253         self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
254    
255     def isRedoAvailable(self):
256         return False
257
258     def paste(self):
259         pass
260    
261
262 class WindowManagerProcessProtocol(protocol.ProcessProtocol):
263     def __init__(self):
264         self.d = defer.Deferred()
265         self.errorData = []
266
267     def errReceived(self, data):
268         self.errorData.append(data)
269
270     def processEnded(self, reason):
271         if reason.value.exitCode == 1:
272             util.log("Error running Window manager:\n%s" % \
273                      ''.join(self.errorData))
274         else:
275             util.log("Window manager shutdown with exit code '%s'" \
276                 % reason.value.exitCode)
277         self.d.callback(None)
Note: See TracBrowser for help on using the browser.