~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to lib/common/console.py

  • Committer: dcoles
  • Date: 2008-08-13 07:26:44 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1017
Console: Refactored a lot of the console stuff into a class to make calls to 
the console from Python a lot nicer. Eventually it should even be able to be 
used by consoleservice but needs a little more work (at the moment it's only 
used for starting the console).
Also added in a few Tutorial specific functions to console such as 'inspect' 
which gives a summary of the the evaluated code block and flush to clear and 
optionally set globals.

Listmake will need to be rerun after this revision due to the new 
lib/common/console.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE
 
2
# Copyright (C) 2007-2008 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
# Module: Console
 
19
# Author: Matt Giuca, Tom Conway, David Coles (refactor)
 
20
# Date: 13/8/2008
 
21
 
 
22
# Mainly refactored out of consoleservice
 
23
 
 
24
import cPickle
 
25
import md5
 
26
import os
 
27
import random
 
28
import socket
 
29
import uuid
 
30
 
 
31
import conf
 
32
from common import chat
 
33
 
 
34
trampoline_path = os.path.join(conf.ivle_install_dir, "bin/trampoline")
 
35
trampoline_path = os.path.join(conf.ivle_install_dir, "bin/trampoline")
 
36
python_path = "/usr/bin/python"                     # Within jail
 
37
console_dir = "/opt/ivle/scripts"                   # Within jail
 
38
console_path = "/opt/ivle/scripts/python-console"   # Within jail
 
39
 
 
40
class ConsoleError(Exception):
 
41
    """ The console failed in some way. This is bad. """
 
42
    def __init__(self, value):
 
43
        self.value = value
 
44
    def __str__(self):
 
45
        return repr(self.value)
 
46
 
 
47
class ConsoleException(Exception):
 
48
    """ The code being exectuted on the console returned an exception. 
 
49
    """
 
50
    def __init__(self, value):
 
51
        self.value = value
 
52
    def __str__(self):
 
53
        return repr(self.value)
 
54
 
 
55
class Console(object):
 
56
    """ Provides a nice python interface to the console
 
57
    """
 
58
    def __init__(self, uid, jail_path, working_dir):
 
59
        """Starts up a console service for user uid, inside chroot jail 
 
60
        jail_path with work directory of working_dir
 
61
        """
 
62
        self.uid = uid
 
63
        self.jail_path = jail_path
 
64
        self.working_dir = working_dir
 
65
        self.restart()
 
66
 
 
67
    def restart(self):
 
68
        # TODO: Check if we are already running a console. If we are shut it 
 
69
        # down first.
 
70
 
 
71
        # TODO: Figure out the host name the console server is running on.
 
72
        self.host = socket.gethostname()
 
73
 
 
74
        # Create magic
 
75
        # TODO
 
76
        self.magic = md5.new(uuid.uuid4().bytes).digest().encode('hex')
 
77
 
 
78
        # Try to find a free port on the server.
 
79
        # Just try some random ports in the range [3000,8000)
 
80
        # until we either succeed, or give up. If you think this
 
81
        # sounds risky, it isn't:
 
82
        # For N ports (e.g. 5000) with k (e.g. 100) in use, the
 
83
        # probability of failing to find a free port in t (e.g. 5) tries
 
84
        # is (k / N) ** t (e.g. 3.2*10e-9).
 
85
 
 
86
        tries = 0
 
87
        while tries < 5:
 
88
            self.port = int(random.uniform(3000, 8000))
 
89
 
 
90
            # Start the console server (port, magic)
 
91
            # trampoline usage: tramp uid jail_dir working_dir script_path args
 
92
            # console usage:    python-console port magic
 
93
            cmd = ' '.join([trampoline_path, str(self.uid), self.jail_path,
 
94
                            console_dir, python_path, console_path,
 
95
                            str(self.port), str(self.magic), 
 
96
                            self.working_dir])
 
97
 
 
98
            res = os.system(cmd)
 
99
 
 
100
            if res == 0:
 
101
                # success
 
102
                break;
 
103
 
 
104
            tries += 1
 
105
 
 
106
        # If we can't start the console after 5 attemps (can't find a free port 
 
107
        # during random probing, syntax errors, segfaults) throw an exception.
 
108
        if tries == 5:
 
109
            raise ConsoleError("Unable to start console service!")
 
110
 
 
111
    def __chat(self, cmd, args):
 
112
        """ A wrapper around chat.chat to comunicate directly with the 
 
113
        console.
 
114
        """
 
115
        try:
 
116
            response = chat.chat(self.host, self.port,
 
117
                {'cmd': cmd, 'text': args}, self.magic)
 
118
        except socket.error, (enumber, estring):
 
119
            if enumber == errno.ECONNREFUSED:
 
120
                # Timeout
 
121
                raise ConsoleError(
 
122
                    "The IVLE console has timed out due to inactivity")
 
123
            else:
 
124
                # Some other error - probably serious
 
125
                raise socket.error, (enumber, estring)
 
126
        except cjson.DecodeError:
 
127
            # Couldn't decode the JSON
 
128
            raise ConsoleError(
 
129
                "Communication to console process lost")
 
130
 
 
131
        # Look for user errors
 
132
        if 'exc' in response:
 
133
            raise ConsoleException(response['exc'])
 
134
        
 
135
        return response
 
136
 
 
137
    def chat(self, code=''):
 
138
        """ Executes a partial block of code """
 
139
        return self.__chat('chat', code)
 
140
 
 
141
    def block(self, code):
 
142
        """ Executes a block of code and returns the output """
 
143
        block = self.__chat('block', code)
 
144
        if 'output' in block:
 
145
            return block['output']
 
146
        elif 'okay' in block:
 
147
            return
 
148
        else:
 
149
            raise ConsoleException("Bad response from console: %s"%str(block))
 
150
 
 
151
    def flush(self, globs=None):
 
152
        """ Resets the consoles globals() to the default and optionally augment 
 
153
        them with a dictionary simple globals. (Must be able to be pickled)
 
154
        """
 
155
        # Pickle the globals
 
156
        pickled_globs = {}
 
157
        if globs is not None:
 
158
            for g in globs:
 
159
                pickled_globs[g] = cPickle.dumps(globs[g])
 
160
 
 
161
        flush = self.__chat('flush', pickled_globs)
 
162
        if 'response' in flush and flush['response'] == 'okay':
 
163
            return
 
164
        else:
 
165
            raise ConsoleError("Bad response from console: %s"%str(flush))
 
166
 
 
167
    def call(self, function, *args, **kwargs):
 
168
        """ Calls a function in the python console """
 
169
        call_args = {
 
170
            'function': cPickle.dumps(function),
 
171
            'args':args,
 
172
            'kwargs': kwargs}
 
173
        response = self.__chat('call', call_args)
 
174
        if 'output' in response:
 
175
            return response['output']
 
176
        else:
 
177
            raise ConsoleError(
 
178
                "Bad response from console: %s"%str(response))
 
179
 
 
180
    def inspect(self, code=''):
 
181
        """ Runs a block of code in the python console returning a dictionary 
 
182
        summary of the evaluation. Currently this includes the values of 
 
183
        stdout, stderr, simple global varibles.
 
184
        If an exception was thrown then this dictionary also includes a 
 
185
        exception dictionary containg a traceback string and the exception 
 
186
        except.
 
187
        """
 
188
        inspection = self.__chat('inspect', code)
 
189
       
 
190
        # Unpickle the globals
 
191
        for g in inspection['globals']:
 
192
            inspection['globals'][g] = cPickle.loads(inspection['globals'][g])
 
193
        
 
194
        # Unpickle any exceptions
 
195
        if 'exception' in inspection:
 
196
            inspection['exception']['except'] = \
 
197
                cPickle.loads(inspection['exception']['except'])
 
198
 
 
199
        return inspection
 
200
    
 
201