##// END OF EJS Templates
Added test in ipcluster.py to see if the platform is win32. If so,...
Brian Granger -
Show More
@@ -1,342 +1,347
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3
4 4 """Start an IPython cluster conveniently, either locally or remotely.
5 5
6 6 Basic usage
7 7 -----------
8 8
9 9 For local operation, the simplest mode of usage is:
10 10
11 11 %prog -n N
12 12
13 13 where N is the number of engines you want started.
14 14
15 15 For remote operation, you must call it with a cluster description file:
16 16
17 17 %prog -f clusterfile.py
18 18
19 19 The cluster file is a normal Python script which gets run via execfile(). You
20 20 can have arbitrary logic in it, but all that matters is that at the end of the
21 21 execution, it declares the variables 'controller', 'engines', and optionally
22 22 'sshx'. See the accompanying examples for details on what these variables must
23 23 contain.
24 24
25 25
26 26 Notes
27 27 -----
28 28
29 29 WARNING: this code is still UNFINISHED and EXPERIMENTAL! It is incomplete,
30 30 some listed options are not really implemented, and all of its interfaces are
31 31 subject to change.
32 32
33 33 When operating over SSH for a remote cluster, this program relies on the
34 34 existence of a particular script called 'sshx'. This script must live in the
35 35 target systems where you'll be running your controller and engines, and is
36 36 needed to configure your PATH and PYTHONPATH variables for further execution of
37 37 python code at the other end of an SSH connection. The script can be as simple
38 38 as:
39 39
40 40 #!/bin/sh
41 41 . $HOME/.bashrc
42 42 "$@"
43 43
44 44 which is the default one provided by IPython. You can modify this or provide
45 45 your own. Since it's quite likely that for different clusters you may need
46 46 this script to configure things differently or that it may live in different
47 47 locations, its full path can be set in the same file where you define the
48 48 cluster setup. IPython's order of evaluation for this variable is the
49 49 following:
50 50
51 51 a) Internal default: 'sshx'. This only works if it is in the default system
52 52 path which SSH sets up in non-interactive mode.
53 53
54 54 b) Environment variable: if $IPYTHON_SSHX is defined, this overrides the
55 55 internal default.
56 56
57 57 c) Variable 'sshx' in the cluster configuration file: finally, this will
58 58 override the previous two values.
59 59
60 60 This code is Unix-only, with precious little hope of any of this ever working
61 61 under Windows, since we need SSH from the ground up, we background processes,
62 62 etc. Ports of this functionality to Windows are welcome.
63 63
64 64
65 65 Call summary
66 66 ------------
67 67
68 68 %prog [options]
69 69 """
70 70
71 71 __docformat__ = "restructuredtext en"
72 72
73 73 #-------------------------------------------------------------------------------
74 74 # Copyright (C) 2008 The IPython Development Team
75 75 #
76 76 # Distributed under the terms of the BSD License. The full license is in
77 77 # the file COPYING, distributed as part of this software.
78 78 #-------------------------------------------------------------------------------
79 79
80 80 #-------------------------------------------------------------------------------
81 81 # Stdlib imports
82 82 #-------------------------------------------------------------------------------
83 83
84 84 import os
85 85 import signal
86 86 import sys
87 87 import time
88 88
89 89 from optparse import OptionParser
90 90 from subprocess import Popen,call
91 91
92 92 #---------------------------------------------------------------------------
93 93 # IPython imports
94 94 #---------------------------------------------------------------------------
95 95 from IPython.tools import utils
96 96 from IPython.genutils import get_ipython_dir
97 97
98 98 #---------------------------------------------------------------------------
99 99 # Normal code begins
100 100 #---------------------------------------------------------------------------
101 101
102 102 def parse_args():
103 103 """Parse command line and return opts,args."""
104 104
105 105 parser = OptionParser(usage=__doc__)
106 106 newopt = parser.add_option # shorthand
107 107
108 108 newopt("--controller-port", type="int", dest="controllerport",
109 109 help="the TCP port the controller is listening on")
110 110
111 111 newopt("--controller-ip", type="string", dest="controllerip",
112 112 help="the TCP ip address of the controller")
113 113
114 114 newopt("-n", "--num", type="int", dest="n",default=2,
115 115 help="the number of engines to start")
116 116
117 117 newopt("--engine-port", type="int", dest="engineport",
118 118 help="the TCP port the controller will listen on for engine "
119 119 "connections")
120 120
121 121 newopt("--engine-ip", type="string", dest="engineip",
122 122 help="the TCP ip address the controller will listen on "
123 123 "for engine connections")
124 124
125 125 newopt("--mpi", type="string", dest="mpi",
126 126 help="use mpi with package: for instance --mpi=mpi4py")
127 127
128 128 newopt("-l", "--logfile", type="string", dest="logfile",
129 129 help="log file name")
130 130
131 131 newopt('-f','--cluster-file',dest='clusterfile',
132 132 help='file describing a remote cluster')
133 133
134 134 return parser.parse_args()
135 135
136 136 def numAlive(controller,engines):
137 137 """Return the number of processes still alive."""
138 138 retcodes = [controller.poll()] + \
139 139 [e.poll() for e in engines]
140 140 return retcodes.count(None)
141 141
142 142 stop = lambda pid: os.kill(pid,signal.SIGINT)
143 143 kill = lambda pid: os.kill(pid,signal.SIGTERM)
144 144
145 145 def cleanup(clean,controller,engines):
146 146 """Stop the controller and engines with the given cleanup method."""
147 147
148 148 for e in engines:
149 149 if e.poll() is None:
150 150 print 'Stopping engine, pid',e.pid
151 151 clean(e.pid)
152 152 if controller.poll() is None:
153 153 print 'Stopping controller, pid',controller.pid
154 154 clean(controller.pid)
155 155
156 156
157 157 def ensureDir(path):
158 158 """Ensure a directory exists or raise an exception."""
159 159 if not os.path.isdir(path):
160 160 os.makedirs(path)
161 161
162 162
163 163 def startMsg(control_host,control_port=10105):
164 164 """Print a startup message"""
165 165 print
166 166 print 'Your cluster is up and running.'
167 167 print
168 168 print 'For interactive use, you can make a MultiEngineClient with:'
169 169 print
170 170 print 'from IPython.kernel import client'
171 171 print "mec = client.MultiEngineClient()"
172 172 print
173 173 print 'You can then cleanly stop the cluster from IPython using:'
174 174 print
175 175 print 'mec.kill(controller=True)'
176 176 print
177 177
178 178
179 179 def clusterLocal(opt,arg):
180 180 """Start a cluster on the local machine."""
181 181
182 182 # Store all logs inside the ipython directory
183 183 ipdir = get_ipython_dir()
184 184 pjoin = os.path.join
185 185
186 186 logfile = opt.logfile
187 187 if logfile is None:
188 188 logdir_base = pjoin(ipdir,'log')
189 189 ensureDir(logdir_base)
190 190 logfile = pjoin(logdir_base,'ipcluster-')
191 191
192 192 print 'Starting controller:',
193 193 controller = Popen(['ipcontroller','--logfile',logfile,'-x','-y'])
194 194 print 'Controller PID:',controller.pid
195 195
196 196 print 'Starting engines: ',
197 197 time.sleep(5)
198 198
199 199 englogfile = '%s%s-' % (logfile,controller.pid)
200 200 mpi = opt.mpi
201 201 if mpi: # start with mpi - killing the engines with sigterm will not work if you do this
202 202 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi',
203 203 mpi, '--logfile',englogfile])]
204 204 # engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi])]
205 205 else: # do what we would normally do
206 206 engines = [ Popen(['ipengine','--logfile',englogfile])
207 207 for i in range(opt.n) ]
208 208 eids = [e.pid for e in engines]
209 209 print 'Engines PIDs: ',eids
210 210 print 'Log files: %s*' % englogfile
211 211
212 212 proc_ids = eids + [controller.pid]
213 213 procs = engines + [controller]
214 214
215 215 grpid = os.getpgrp()
216 216 try:
217 217 startMsg('127.0.0.1')
218 218 print 'You can also hit Ctrl-C to stop it, or use from the cmd line:'
219 219 print
220 220 print 'kill -INT',grpid
221 221 print
222 222 try:
223 223 while True:
224 224 time.sleep(5)
225 225 except:
226 226 pass
227 227 finally:
228 228 print 'Stopping cluster. Cleaning up...'
229 229 cleanup(stop,controller,engines)
230 230 for i in range(4):
231 231 time.sleep(i+2)
232 232 nZombies = numAlive(controller,engines)
233 233 if nZombies== 0:
234 234 print 'OK: All processes cleaned up.'
235 235 break
236 236 print 'Trying again, %d processes did not stop...' % nZombies
237 237 cleanup(kill,controller,engines)
238 238 if numAlive(controller,engines) == 0:
239 239 print 'OK: All processes cleaned up.'
240 240 break
241 241 else:
242 242 print '*'*75
243 243 print 'ERROR: could not kill some processes, try to do it',
244 244 print 'manually.'
245 245 zombies = []
246 246 if controller.returncode is None:
247 247 print 'Controller is alive: pid =',controller.pid
248 248 zombies.append(controller.pid)
249 249 liveEngines = [ e for e in engines if e.returncode is None ]
250 250 for e in liveEngines:
251 251 print 'Engine is alive: pid =',e.pid
252 252 zombies.append(e.pid)
253 253 print
254 254 print 'Zombie summary:',' '.join(map(str,zombies))
255 255
256 256 def clusterRemote(opt,arg):
257 257 """Start a remote cluster over SSH"""
258 258
259 259 # B. Granger, 9/3/08
260 260 # The launching of a remote cluster using SSH and a clusterfile
261 261 # is broken. Because it won't be fixed before the 0.9 release,
262 262 # we are removing it. For now, we just print a message to the
263 263 # user and abort.
264 264
265 265 print """The launching of a remote IPython cluster using SSL
266 266 and a clusterfile has been removed in this release.
267 267 It has been broken for a while and we are in the process
268 268 of building a new process management system that will be
269 269 used to provide a more robust way of starting an IPython
270 270 cluster.
271 271
272 272 For now remote clusters have to be launched using ipcontroller
273 273 and ipengine separately.
274 274 """
275 275 sys.exit(1)
276 276
277 277 # Load the remote cluster configuration
278 278 clConfig = {}
279 279 execfile(opt.clusterfile,clConfig)
280 280 contConfig = clConfig['controller']
281 281 engConfig = clConfig['engines']
282 282 # Determine where to find sshx:
283 283 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
284 284
285 285 # Store all logs inside the ipython directory
286 286 ipdir = get_ipython_dir()
287 287 pjoin = os.path.join
288 288
289 289 logfile = opt.logfile
290 290 if logfile is None:
291 291 logdir_base = pjoin(ipdir,'log')
292 292 ensureDir(logdir_base)
293 293 logfile = pjoin(logdir_base,'ipcluster')
294 294
295 295 # Append this script's PID to the logfile name always
296 296 logfile = '%s-%s' % (logfile,os.getpid())
297 297
298 298 print 'Starting controller:'
299 299 # Controller data:
300 300 xsys = os.system
301 301
302 302 contHost = contConfig['host']
303 303 contLog = '%s-con-%s-' % (logfile,contHost)
304 304 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
305 305 (contHost,sshx,contLog)
306 306 #print 'cmd:<%s>' % cmd # dbg
307 307 xsys(cmd)
308 308 time.sleep(2)
309 309
310 310 print 'Starting engines: '
311 311 for engineHost,engineData in engConfig.iteritems():
312 312 if isinstance(engineData,int):
313 313 numEngines = engineData
314 314 else:
315 315 raise NotImplementedError('port configuration not finished for engines')
316 316
317 317 print 'Sarting %d engines on %s' % (numEngines,engineHost)
318 318 engLog = '%s-eng-%s-' % (logfile,engineHost)
319 319 for i in range(numEngines):
320 320 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
321 321 (engineHost,sshx,contHost,engLog)
322 322 #print 'cmd:<%s>' % cmd # dbg
323 323 xsys(cmd)
324 324 # Wait after each host a little bit
325 325 time.sleep(1)
326 326
327 327 startMsg(contConfig['host'])
328 328
329 329 def main():
330 330 """Main driver for the two big options: local or remote cluster."""
331 331
332 if sys.platform=='win32':
333 print """ipcluster does not work on Microsoft Windows. Please start
334 your IPython cluster using the ipcontroller and ipengine scripts."""
335 sys.exit(1)
336
332 337 opt,arg = parse_args()
333 338
334 339 clusterfile = opt.clusterfile
335 340 if clusterfile:
336 341 clusterRemote(opt,arg)
337 342 else:
338 343 clusterLocal(opt,arg)
339 344
340 345
341 346 if __name__=='__main__':
342 347 main()
@@ -1,105 +1,106
1 1 #!python
2 2 """Windows-specific part of the installation"""
3 3
4 4 import os, sys, shutil
5 5 pjoin = os.path.join
6 6
7 7 def mkshortcut(target,description,link_file,*args,**kw):
8 8 """make a shortcut if it doesn't exist, and register its creation"""
9 9
10 10 create_shortcut(target, description, link_file,*args,**kw)
11 11 file_created(link_file)
12 12
13 13 def install():
14 14 """Routine to be run by the win32 installer with the -install switch."""
15 15
16 16 from IPython.Release import version
17 17
18 18 # Get some system constants
19 19 prefix = sys.prefix
20 20 python = pjoin(prefix, 'python.exe')
21 21
22 22 # Lookup path to common startmenu ...
23 23 ip_start_menu = pjoin(get_special_folder_path('CSIDL_COMMON_PROGRAMS'), 'IPython')
24 24 # Create IPython entry ...
25 25 if not os.path.isdir(ip_start_menu):
26 26 os.mkdir(ip_start_menu)
27 27 directory_created(ip_start_menu)
28 28
29 29 # Create .py and .bat files to make things available from
30 # the Windows command line
30 # the Windows command line. Thanks to the Twisted project
31 # for this logic!
31 32 programs = [
32 33 'ipython',
33 34 'iptest',
34 35 'ipcontroller',
35 36 'ipengine',
36 37 'ipcluster',
37 38 'ipythonx',
38 39 'ipython-wx',
39 40 'irunner'
40 41 ]
41 42 scripts = pjoin(prefix,'scripts')
42 43 for program in programs:
43 44 raw = pjoin(scripts, program)
44 45 bat = raw + '.bat'
45 46 py = raw + '.py'
46 47 # Create .py versions of the scripts
47 48 shutil.copy(raw, py)
48 49 # Create .bat files for each of the scripts
49 50 bat_file = file(bat,'w')
50 51 bat_file.write("@%s %s %%*" % (python, py))
51 52 bat_file.close()
52 53
53 54 # Now move onto setting the Start Menu up
54 55 ipybase = pjoin(scripts, 'ipython')
55 56
56 57 link = pjoin(ip_start_menu, 'IPython.lnk')
57 58 cmd = '"%s"' % ipybase
58 59 mkshortcut(python,'IPython',link,cmd)
59 60
60 61 link = pjoin(ip_start_menu, 'pysh.lnk')
61 62 cmd = '"%s" -p sh' % ipybase
62 63 mkshortcut(python,'IPython (command prompt mode)',link,cmd)
63 64
64 65 link = pjoin(ip_start_menu, 'pylab.lnk')
65 66 cmd = '"%s" -pylab' % ipybase
66 67 mkshortcut(python,'IPython (PyLab mode)',link,cmd)
67 68
68 69 link = pjoin(ip_start_menu, 'scipy.lnk')
69 70 cmd = '"%s" -pylab -p scipy' % ipybase
70 71 mkshortcut(python,'IPython (scipy profile)',link,cmd)
71 72
72 73 link = pjoin(ip_start_menu, 'IPython test suite.lnk')
73 74 cmd = '"%s" -vv' % pjoin(scripts, 'iptest')
74 75 mkshortcut(python,'Run the IPython test suite',link,cmd)
75 76
76 77 link = pjoin(ip_start_menu, 'ipcontroller.lnk')
77 78 cmd = '"%s" -xy' % pjoin(scripts, 'ipcontroller')
78 79 mkshortcut(python,'IPython controller',link,cmd)
79 80
80 81 link = pjoin(ip_start_menu, 'ipengine.lnk')
81 82 cmd = '"%s"' % pjoin(scripts, 'ipengine')
82 83 mkshortcut(python,'IPython engine',link,cmd)
83 84
84 85 # Create documentation shortcuts ...
85 86 t = prefix + r'\share\doc\ipython\manual\ipython.pdf'
86 87 f = ip_start_menu + r'\Manual in PDF.lnk'
87 88 mkshortcut(t,r'IPython Manual - PDF-Format',f)
88 89
89 90 t = prefix + r'\share\doc\ipython\manual\html\index.html'
90 91 f = ip_start_menu + r'\Manual in HTML.lnk'
91 92 mkshortcut(t,'IPython Manual - HTML-Format',f)
92 93
93 94
94 95 def remove():
95 96 """Routine to be run by the win32 installer with the -remove switch."""
96 97 pass
97 98
98 99 # main()
99 100 if len(sys.argv) > 1:
100 101 if sys.argv[1] == '-install':
101 102 install()
102 103 elif sys.argv[1] == '-remove':
103 104 remove()
104 105 else:
105 106 print "Script was called with option %s" % sys.argv[1]
General Comments 0
You need to be logged in to leave comments. Login now