| 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-2008 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 |
The main module for node workers. |
|---|
| 28 |
|
|---|
| 29 |
""" |
|---|
| 30 |
|
|---|
| 31 |
|
|---|
| 32 |
CONFIG_PATH = "/etc/asyncluster.conf" |
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
class Manager(object): |
|---|
| 36 |
""" |
|---|
| 37 |
I manage a node client. Instantiate me with I{headless} set C{True} if |
|---|
| 38 |
there will be no GUI for user logins or display management. |
|---|
| 39 |
|
|---|
| 40 |
@ivar config: A L{configobj} config object loaded from the config file. |
|---|
| 41 |
|
|---|
| 42 |
""" |
|---|
| 43 |
def __init__(self, headless=False, duration=None): |
|---|
| 44 |
import configobj |
|---|
| 45 |
self.config = configobj.ConfigObj(CONFIG_PATH) |
|---|
| 46 |
if headless: |
|---|
| 47 |
self.gui = None |
|---|
| 48 |
else: |
|---|
| 49 |
import gui |
|---|
| 50 |
self.gui = gui |
|---|
| 51 |
# Regular reactor import comes after possible Qt reactor integration in |
|---|
| 52 |
# gui module |
|---|
| 53 |
from twisted.internet import reactor |
|---|
| 54 |
reactor.callWhenRunning(self.startup) |
|---|
| 55 |
if isinstance(duration, (float, int)): |
|---|
| 56 |
reactor.callLater(float(duration), reactor.stop) |
|---|
| 57 |
reactor.run() |
|---|
| 58 |
|
|---|
| 59 |
def startup(self): |
|---|
| 60 |
""" |
|---|
| 61 |
Instantiates a session-capable client and connects it to the server. |
|---|
| 62 |
""" |
|---|
| 63 |
def gotSessionMgr(sessionMgr): |
|---|
| 64 |
self.sessionMgr = sessionMgr |
|---|
| 65 |
if self.gui: |
|---|
| 66 |
self.loginWindow = self.gui.LoginWindow(self) |
|---|
| 67 |
|
|---|
| 68 |
import client |
|---|
| 69 |
self.client = client.Client(self, session=True) |
|---|
| 70 |
d = self.client.connect() |
|---|
| 71 |
d.addCallback(lambda p: p.callRemote('getSessionManager')) |
|---|
| 72 |
d.addCallback(gotSessionMgr) |
|---|
| 73 |
return d |
|---|
| 74 |
|
|---|
| 75 |
def sessionBegin(self, user, password): |
|---|
| 76 |
""" |
|---|
| 77 |
Requests a session for the specified I{user}, authenticated with the |
|---|
| 78 |
supplied I{password}. |
|---|
| 79 |
""" |
|---|
| 80 |
def gotSessionAnswer(approved): |
|---|
| 81 |
if approved: |
|---|
| 82 |
if hasattr(self, 'loginWindow'): |
|---|
| 83 |
self.loginWindow.hide() |
|---|
| 84 |
self.sessionWindow = self.gui.SessionWindow(self, user) |
|---|
| 85 |
self.sessionWindow.show() |
|---|
| 86 |
self.activeUser = user |
|---|
| 87 |
d = self.sessionMgr.callRemote('timeLeft') |
|---|
| 88 |
d.addCallback(self.sessionUpdate) |
|---|
| 89 |
return d |
|---|
| 90 |
|
|---|
| 91 |
d = self.sessionMgr.callRemote('begin', user, password) |
|---|
| 92 |
d.addCallback(gotSessionAnswer) |
|---|
| 93 |
return d |
|---|
| 94 |
|
|---|
| 95 |
def sessionUpdate(self, hoursLeft): |
|---|
| 96 |
""" |
|---|
| 97 |
Updates the session. |
|---|
| 98 |
""" |
|---|
| 99 |
if self.activeUser is None: |
|---|
| 100 |
return |
|---|
| 101 |
if hoursLeft > 0.0: |
|---|
| 102 |
if hasattr(self, 'sessionWindow'): |
|---|
| 103 |
self.sessionWindow.update(hoursLeft) |
|---|
| 104 |
else: |
|---|
| 105 |
self.sessionEnd(callServer=False) |
|---|
| 106 |
|
|---|
| 107 |
def sessionEnd(self, callServer=True): |
|---|
| 108 |
""" |
|---|
| 109 |
Ends the session, returning a deferred that fires when I'm ready for a |
|---|
| 110 |
new session. |
|---|
| 111 |
""" |
|---|
| 112 |
if hasattr(self, 'loginWindow'): |
|---|
| 113 |
self.loginWindow.show() |
|---|
| 114 |
self.loginWindow.repaint() |
|---|
| 115 |
if hasattr(self, 'sessionWindow'): |
|---|
| 116 |
self.sessionWindow.wmStop() |
|---|
| 117 |
self.sessionWindow.close() |
|---|
| 118 |
del self.sessionWindow |
|---|
| 119 |
self.activeUser = None |
|---|
| 120 |
if callServer: |
|---|
| 121 |
return self.sessionMgr.callRemote('end') |
|---|
| 122 |
from twisted.internet import defer |
|---|
| 123 |
return defer.succeed(None) |
|---|
| 124 |
|
|---|
| 125 |
def message(self, msg): |
|---|
| 126 |
""" |
|---|
| 127 |
If there is a session underway, displays the message on it. |
|---|
| 128 |
""" |
|---|
| 129 |
if hasattr(self, 'sessionWindow'): |
|---|
| 130 |
self.sessionWindow.status(msg) |
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
def run(): |
|---|
| 134 |
Manager() |
|---|
| 135 |
|
|---|
| 136 |
def runHeadless(duration=None): |
|---|
| 137 |
Manager(headless=True, duration=duration) |
|---|