##// END OF EJS Templates
Added better documentation to command line programs.
Brian Granger -
Show More
@@ -1,459 +1,471 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The ipcluster application.
4 The ipcluster application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import logging
18 import logging
19 import os
19 import os
20 import signal
20 import signal
21 import sys
21 import sys
22
22
23 if os.name=='posix':
23 if os.name=='posix':
24 from twisted.scripts._twistd_unix import daemonize
24 from twisted.scripts._twistd_unix import daemonize
25
25
26 from IPython.core import release
26 from IPython.core import release
27 from IPython.external import argparse
27 from IPython.external import argparse
28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
29 from IPython.utils.importstring import import_item
29 from IPython.utils.importstring import import_item
30
30
31 from IPython.kernel.clusterdir import (
31 from IPython.kernel.clusterdir import (
32 ApplicationWithClusterDir, ClusterDirError, PIDFileError
32 ApplicationWithClusterDir, ClusterDirError, PIDFileError
33 )
33 )
34
34
35 from twisted.internet import reactor, defer
35 from twisted.internet import reactor, defer
36 from twisted.python import log, failure
36 from twisted.python import log, failure
37
37
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # The ipcluster application
40 # The ipcluster application
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43
43
44 # Exit codes for ipcluster
44 # Exit codes for ipcluster
45
45
46 # This will be the exit code if the ipcluster appears to be running because
46 # This will be the exit code if the ipcluster appears to be running because
47 # a .pid file exists
47 # a .pid file exists
48 ALREADY_STARTED = 10
48 ALREADY_STARTED = 10
49
49
50 # This will be the exit code if ipcluster stop is run, but there is not .pid
50 # This will be the exit code if ipcluster stop is run, but there is not .pid
51 # file to be found.
51 # file to be found.
52 ALREADY_STOPPED = 11
52 ALREADY_STOPPED = 11
53
53
54
54
55 class IPClusterCLLoader(ArgParseConfigLoader):
55 class IPClusterCLLoader(ArgParseConfigLoader):
56
56
57 def _add_arguments(self):
57 def _add_arguments(self):
58 # This has all the common options that all subcommands use
58 # This has all the common options that all subcommands use
59 parent_parser1 = argparse.ArgumentParser(add_help=False)
59 parent_parser1 = argparse.ArgumentParser(add_help=False)
60 parent_parser1.add_argument('--ipython-dir',
60 parent_parser1.add_argument('--ipython-dir',
61 dest='Global.ipython_dir',type=unicode,
61 dest='Global.ipython_dir',type=unicode,
62 help='Set to override default location of Global.ipython_dir.',
62 help='Set to override default location of Global.ipython_dir.',
63 default=NoConfigDefault,
63 default=NoConfigDefault,
64 metavar='Global.ipython_dir')
64 metavar='Global.ipython_dir')
65 parent_parser1.add_argument('--log-level',
65 parent_parser1.add_argument('--log-level',
66 dest="Global.log_level",type=int,
66 dest="Global.log_level",type=int,
67 help='Set the log level (0,10,20,30,40,50). Default is 30.',
67 help='Set the log level (0,10,20,30,40,50). Default is 30.',
68 default=NoConfigDefault,
68 default=NoConfigDefault,
69 metavar='Global.log_level')
69 metavar='Global.log_level')
70
70
71 # This has all the common options that other subcommands use
71 # This has all the common options that other subcommands use
72 parent_parser2 = argparse.ArgumentParser(add_help=False)
72 parent_parser2 = argparse.ArgumentParser(add_help=False)
73 parent_parser2.add_argument('-p','--profile',
73 parent_parser2.add_argument('-p','--profile',
74 dest='Global.profile',type=unicode,
74 dest='Global.profile',type=unicode,
75 help='The string name of the profile to be used. This determines '
75 help='The string name of the profile to be used. This determines '
76 'the name of the cluster dir as: cluster_<profile>. The default profile '
76 'the name of the cluster dir as: cluster_<profile>. The default profile '
77 'is named "default". The cluster directory is resolve this way '
77 'is named "default". The cluster directory is resolve this way '
78 'if the --cluster-dir option is not used.',
78 'if the --cluster-dir option is not used.',
79 default=NoConfigDefault,
79 default=NoConfigDefault,
80 metavar='Global.profile')
80 metavar='Global.profile')
81 parent_parser2.add_argument('--cluster-dir',
81 parent_parser2.add_argument('--cluster-dir',
82 dest='Global.cluster_dir',type=unicode,
82 dest='Global.cluster_dir',type=unicode,
83 help='Set the cluster dir. This overrides the logic used by the '
83 help='Set the cluster dir. This overrides the logic used by the '
84 '--profile option.',
84 '--profile option.',
85 default=NoConfigDefault,
85 default=NoConfigDefault,
86 metavar='Global.cluster_dir'),
86 metavar='Global.cluster_dir'),
87 parent_parser2.add_argument('--work-dir',
87 parent_parser2.add_argument('--work-dir',
88 dest='Global.work_dir',type=unicode,
88 dest='Global.work_dir',type=unicode,
89 help='Set the working dir for the process.',
89 help='Set the working dir for the process.',
90 default=NoConfigDefault,
90 default=NoConfigDefault,
91 metavar='Global.work_dir')
91 metavar='Global.work_dir')
92 parent_parser2.add_argument('--log-to-file',
92 parent_parser2.add_argument('--log-to-file',
93 action='store_true', dest='Global.log_to_file',
93 action='store_true', dest='Global.log_to_file',
94 default=NoConfigDefault,
94 default=NoConfigDefault,
95 help='Log to a file in the log directory (default is stdout)'
95 help='Log to a file in the log directory (default is stdout)'
96 )
96 )
97
97
98 subparsers = self.parser.add_subparsers(
98 subparsers = self.parser.add_subparsers(
99 dest='Global.subcommand',
99 dest='Global.subcommand',
100 title='ipcluster subcommands',
100 title='ipcluster subcommands',
101 description='ipcluster has a variety of subcommands. '
101 description='ipcluster has a variety of subcommands. '
102 'The general way of running ipcluster is "ipcluster <cmd> '
102 'The general way of running ipcluster is "ipcluster <cmd> '
103 ' [options]""',
103 ' [options]""',
104 help='For more help, type "ipcluster <cmd> -h"')
104 help='For more help, type "ipcluster <cmd> -h"')
105
105
106 parser_list = subparsers.add_parser(
106 parser_list = subparsers.add_parser(
107 'list',
107 'list',
108 help='List all clusters in cwd and ipython_dir.',
108 help='List all clusters in cwd and ipython_dir.',
109 parents=[parent_parser1]
109 parents=[parent_parser1]
110 )
110 )
111
111
112 parser_create = subparsers.add_parser(
112 parser_create = subparsers.add_parser(
113 'create',
113 'create',
114 help='Create a new cluster directory.',
114 help='Create a new cluster directory.',
115 parents=[parent_parser1, parent_parser2]
115 parents=[parent_parser1, parent_parser2]
116 )
116 )
117 parser_create.add_argument(
117 parser_create.add_argument(
118 '--reset-config',
118 '--reset-config',
119 dest='Global.reset_config', action='store_true',
119 dest='Global.reset_config', action='store_true',
120 default=NoConfigDefault,
120 default=NoConfigDefault,
121 help='Recopy the default config files to the cluster directory. '
121 help='Recopy the default config files to the cluster directory. '
122 'You will loose any modifications you have made to these files.'
122 'You will loose any modifications you have made to these files.'
123 )
123 )
124
124
125 parser_start = subparsers.add_parser(
125 parser_start = subparsers.add_parser(
126 'start',
126 'start',
127 help='Start a cluster.',
127 help='Start a cluster.',
128 parents=[parent_parser1, parent_parser2]
128 parents=[parent_parser1, parent_parser2]
129 )
129 )
130 parser_start.add_argument(
130 parser_start.add_argument(
131 '-n', '--number',
131 '-n', '--number',
132 type=int, dest='Global.n',
132 type=int, dest='Global.n',
133 default=NoConfigDefault,
133 default=NoConfigDefault,
134 help='The number of engines to start.',
134 help='The number of engines to start.',
135 metavar='Global.n'
135 metavar='Global.n'
136 )
136 )
137 parser_start.add_argument('--clean-logs',
137 parser_start.add_argument('--clean-logs',
138 dest='Global.clean_logs', action='store_true',
138 dest='Global.clean_logs', action='store_true',
139 help='Delete old log flies before starting.',
139 help='Delete old log flies before starting.',
140 default=NoConfigDefault
140 default=NoConfigDefault
141 )
141 )
142 parser_start.add_argument('--no-clean-logs',
142 parser_start.add_argument('--no-clean-logs',
143 dest='Global.clean_logs', action='store_false',
143 dest='Global.clean_logs', action='store_false',
144 help="Don't delete old log flies before starting.",
144 help="Don't delete old log flies before starting.",
145 default=NoConfigDefault
145 default=NoConfigDefault
146 )
146 )
147 parser_start.add_argument('--daemon',
147 parser_start.add_argument('--daemon',
148 dest='Global.daemonize', action='store_true',
148 dest='Global.daemonize', action='store_true',
149 help='Daemonize the ipcluster program. This implies --log-to-file',
149 help='Daemonize the ipcluster program. This implies --log-to-file',
150 default=NoConfigDefault
150 default=NoConfigDefault
151 )
151 )
152 parser_start.add_argument('--no-daemon',
152 parser_start.add_argument('--no-daemon',
153 dest='Global.daemonize', action='store_false',
153 dest='Global.daemonize', action='store_false',
154 help="Dont't daemonize the ipcluster program.",
154 help="Dont't daemonize the ipcluster program.",
155 default=NoConfigDefault
155 default=NoConfigDefault
156 )
156 )
157
157
158 parser_start = subparsers.add_parser(
158 parser_start = subparsers.add_parser(
159 'stop',
159 'stop',
160 help='Stop a cluster.',
160 help='Stop a cluster.',
161 parents=[parent_parser1, parent_parser2]
161 parents=[parent_parser1, parent_parser2]
162 )
162 )
163 parser_start.add_argument('--signal',
163 parser_start.add_argument('--signal',
164 dest='Global.signal', type=int,
164 dest='Global.signal', type=int,
165 help="The signal number to use in stopping the cluster (default=2).",
165 help="The signal number to use in stopping the cluster (default=2).",
166 metavar="Global.signal",
166 metavar="Global.signal",
167 default=NoConfigDefault
167 default=NoConfigDefault
168 )
168 )
169
169
170
170
171 default_config_file_name = u'ipcluster_config.py'
171 default_config_file_name = u'ipcluster_config.py'
172
172
173
173
174 _description = """Start an IPython cluster for parallel computing.\n\n
175
176 An IPython cluster consists of 1 controller and 1 or more engines.
177 This command automates the startup of these processes using a wide
178 range of startup methods (SSH, local processes, PBS, mpiexec,
179 Windows HPC Server 2008). To start a cluster with 4 engines on your
180 local host simply do "ipcluster start -n 4". For more complex usage
181 you will typically do "ipcluster create -p mycluster", then edit
182 configuration files, followed by "ipcluster start -p mycluster -n 4".
183 """
184
185
174 class IPClusterApp(ApplicationWithClusterDir):
186 class IPClusterApp(ApplicationWithClusterDir):
175
187
176 name = u'ipcluster'
188 name = u'ipcluster'
177 description = 'Start an IPython cluster (controller and engines).'
189 description = _description
178 config_file_name = default_config_file_name
190 config_file_name = default_config_file_name
179 default_log_level = logging.INFO
191 default_log_level = logging.INFO
180 auto_create_cluster_dir = False
192 auto_create_cluster_dir = False
181
193
182 def create_default_config(self):
194 def create_default_config(self):
183 super(IPClusterApp, self).create_default_config()
195 super(IPClusterApp, self).create_default_config()
184 self.default_config.Global.controller_launcher = \
196 self.default_config.Global.controller_launcher = \
185 'IPython.kernel.launcher.LocalControllerLauncher'
197 'IPython.kernel.launcher.LocalControllerLauncher'
186 self.default_config.Global.engine_launcher = \
198 self.default_config.Global.engine_launcher = \
187 'IPython.kernel.launcher.LocalEngineSetLauncher'
199 'IPython.kernel.launcher.LocalEngineSetLauncher'
188 self.default_config.Global.n = 2
200 self.default_config.Global.n = 2
189 self.default_config.Global.reset_config = False
201 self.default_config.Global.reset_config = False
190 self.default_config.Global.clean_logs = True
202 self.default_config.Global.clean_logs = True
191 self.default_config.Global.signal = 2
203 self.default_config.Global.signal = 2
192 self.default_config.Global.daemonize = False
204 self.default_config.Global.daemonize = False
193
205
194 def create_command_line_config(self):
206 def create_command_line_config(self):
195 """Create and return a command line config loader."""
207 """Create and return a command line config loader."""
196 return IPClusterCLLoader(
208 return IPClusterCLLoader(
197 description=self.description,
209 description=self.description,
198 version=release.version
210 version=release.version
199 )
211 )
200
212
201 def find_resources(self):
213 def find_resources(self):
202 subcommand = self.command_line_config.Global.subcommand
214 subcommand = self.command_line_config.Global.subcommand
203 if subcommand=='list':
215 if subcommand=='list':
204 self.list_cluster_dirs()
216 self.list_cluster_dirs()
205 # Exit immediately because there is nothing left to do.
217 # Exit immediately because there is nothing left to do.
206 self.exit()
218 self.exit()
207 elif subcommand=='create':
219 elif subcommand=='create':
208 self.auto_create_cluster_dir = True
220 self.auto_create_cluster_dir = True
209 super(IPClusterApp, self).find_resources()
221 super(IPClusterApp, self).find_resources()
210 elif subcommand=='start' or subcommand=='stop':
222 elif subcommand=='start' or subcommand=='stop':
211 self.auto_create_cluster_dir = True
223 self.auto_create_cluster_dir = True
212 try:
224 try:
213 super(IPClusterApp, self).find_resources()
225 super(IPClusterApp, self).find_resources()
214 except ClusterDirError:
226 except ClusterDirError:
215 raise ClusterDirError(
227 raise ClusterDirError(
216 "Could not find a cluster directory. A cluster dir must "
228 "Could not find a cluster directory. A cluster dir must "
217 "be created before running 'ipcluster start'. Do "
229 "be created before running 'ipcluster start'. Do "
218 "'ipcluster create -h' or 'ipcluster list -h' for more "
230 "'ipcluster create -h' or 'ipcluster list -h' for more "
219 "information about creating and listing cluster dirs."
231 "information about creating and listing cluster dirs."
220 )
232 )
221
233
222 def list_cluster_dirs(self):
234 def list_cluster_dirs(self):
223 # Find the search paths
235 # Find the search paths
224 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
236 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
225 if cluster_dir_paths:
237 if cluster_dir_paths:
226 cluster_dir_paths = cluster_dir_paths.split(':')
238 cluster_dir_paths = cluster_dir_paths.split(':')
227 else:
239 else:
228 cluster_dir_paths = []
240 cluster_dir_paths = []
229 try:
241 try:
230 ipython_dir = self.command_line_config.Global.ipython_dir
242 ipython_dir = self.command_line_config.Global.ipython_dir
231 except AttributeError:
243 except AttributeError:
232 ipython_dir = self.default_config.Global.ipython_dir
244 ipython_dir = self.default_config.Global.ipython_dir
233 paths = [os.getcwd(), ipython_dir] + \
245 paths = [os.getcwd(), ipython_dir] + \
234 cluster_dir_paths
246 cluster_dir_paths
235 paths = list(set(paths))
247 paths = list(set(paths))
236
248
237 self.log.info('Searching for cluster dirs in paths: %r' % paths)
249 self.log.info('Searching for cluster dirs in paths: %r' % paths)
238 for path in paths:
250 for path in paths:
239 files = os.listdir(path)
251 files = os.listdir(path)
240 for f in files:
252 for f in files:
241 full_path = os.path.join(path, f)
253 full_path = os.path.join(path, f)
242 if os.path.isdir(full_path) and f.startswith('cluster_'):
254 if os.path.isdir(full_path) and f.startswith('cluster_'):
243 profile = full_path.split('_')[-1]
255 profile = full_path.split('_')[-1]
244 start_cmd = 'ipcluster start -p %s -n 4' % profile
256 start_cmd = 'ipcluster start -p %s -n 4' % profile
245 print start_cmd + " ==> " + full_path
257 print start_cmd + " ==> " + full_path
246
258
247 def pre_construct(self):
259 def pre_construct(self):
248 # IPClusterApp.pre_construct() is where we cd to the working directory.
260 # IPClusterApp.pre_construct() is where we cd to the working directory.
249 super(IPClusterApp, self).pre_construct()
261 super(IPClusterApp, self).pre_construct()
250 config = self.master_config
262 config = self.master_config
251 try:
263 try:
252 daemon = config.Global.daemonize
264 daemon = config.Global.daemonize
253 if daemon:
265 if daemon:
254 config.Global.log_to_file = True
266 config.Global.log_to_file = True
255 except AttributeError:
267 except AttributeError:
256 pass
268 pass
257
269
258 def construct(self):
270 def construct(self):
259 config = self.master_config
271 config = self.master_config
260 subcmd = config.Global.subcommand
272 subcmd = config.Global.subcommand
261 reset = config.Global.reset_config
273 reset = config.Global.reset_config
262 if subcmd == 'list':
274 if subcmd == 'list':
263 return
275 return
264 if subcmd == 'create':
276 if subcmd == 'create':
265 self.log.info('Copying default config files to cluster directory '
277 self.log.info('Copying default config files to cluster directory '
266 '[overwrite=%r]' % (reset,))
278 '[overwrite=%r]' % (reset,))
267 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
279 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
268 if subcmd =='start':
280 if subcmd =='start':
269 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
281 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
270 self.start_logging()
282 self.start_logging()
271 reactor.callWhenRunning(self.start_launchers)
283 reactor.callWhenRunning(self.start_launchers)
272
284
273 def start_launchers(self):
285 def start_launchers(self):
274 config = self.master_config
286 config = self.master_config
275
287
276 # Create the launchers. In both bases, we set the work_dir of
288 # Create the launchers. In both bases, we set the work_dir of
277 # the launcher to the cluster_dir. This is where the launcher's
289 # the launcher to the cluster_dir. This is where the launcher's
278 # subprocesses will be launched. It is not where the controller
290 # subprocesses will be launched. It is not where the controller
279 # and engine will be launched.
291 # and engine will be launched.
280 el_class = import_item(config.Global.engine_launcher)
292 el_class = import_item(config.Global.engine_launcher)
281 self.engine_launcher = el_class(
293 self.engine_launcher = el_class(
282 work_dir=self.cluster_dir, config=config
294 work_dir=self.cluster_dir, config=config
283 )
295 )
284 cl_class = import_item(config.Global.controller_launcher)
296 cl_class = import_item(config.Global.controller_launcher)
285 self.controller_launcher = cl_class(
297 self.controller_launcher = cl_class(
286 work_dir=self.cluster_dir, config=config
298 work_dir=self.cluster_dir, config=config
287 )
299 )
288
300
289 # Setup signals
301 # Setup signals
290 signal.signal(signal.SIGINT, self.sigint_handler)
302 signal.signal(signal.SIGINT, self.sigint_handler)
291
303
292 # Setup the observing of stopping. If the controller dies, shut
304 # Setup the observing of stopping. If the controller dies, shut
293 # everything down as that will be completely fatal for the engines.
305 # everything down as that will be completely fatal for the engines.
294 d1 = self.controller_launcher.observe_stop()
306 d1 = self.controller_launcher.observe_stop()
295 d1.addCallback(self.stop_launchers)
307 d1.addCallback(self.stop_launchers)
296 # But, we don't monitor the stopping of engines. An engine dying
308 # But, we don't monitor the stopping of engines. An engine dying
297 # is just fine and in principle a user could start a new engine.
309 # is just fine and in principle a user could start a new engine.
298 # Also, if we did monitor engine stopping, it is difficult to
310 # Also, if we did monitor engine stopping, it is difficult to
299 # know what to do when only some engines die. Currently, the
311 # know what to do when only some engines die. Currently, the
300 # observing of engine stopping is inconsistent. Some launchers
312 # observing of engine stopping is inconsistent. Some launchers
301 # might trigger on a single engine stopping, other wait until
313 # might trigger on a single engine stopping, other wait until
302 # all stop. TODO: think more about how to handle this.
314 # all stop. TODO: think more about how to handle this.
303
315
304 # Start the controller and engines
316 # Start the controller and engines
305 self._stopping = False # Make sure stop_launchers is not called 2x.
317 self._stopping = False # Make sure stop_launchers is not called 2x.
306 d = self.start_controller()
318 d = self.start_controller()
307 d.addCallback(self.start_engines)
319 d.addCallback(self.start_engines)
308 d.addCallback(self.startup_message)
320 d.addCallback(self.startup_message)
309 # If the controller or engines fail to start, stop everything
321 # If the controller or engines fail to start, stop everything
310 d.addErrback(self.stop_launchers)
322 d.addErrback(self.stop_launchers)
311 return d
323 return d
312
324
313 def startup_message(self, r=None):
325 def startup_message(self, r=None):
314 log.msg("IPython cluster: started")
326 log.msg("IPython cluster: started")
315 return r
327 return r
316
328
317 def start_controller(self, r=None):
329 def start_controller(self, r=None):
318 # log.msg("In start_controller")
330 # log.msg("In start_controller")
319 config = self.master_config
331 config = self.master_config
320 d = self.controller_launcher.start(
332 d = self.controller_launcher.start(
321 cluster_dir=config.Global.cluster_dir
333 cluster_dir=config.Global.cluster_dir
322 )
334 )
323 return d
335 return d
324
336
325 def start_engines(self, r=None):
337 def start_engines(self, r=None):
326 # log.msg("In start_engines")
338 # log.msg("In start_engines")
327 config = self.master_config
339 config = self.master_config
328 d = self.engine_launcher.start(
340 d = self.engine_launcher.start(
329 config.Global.n,
341 config.Global.n,
330 cluster_dir=config.Global.cluster_dir
342 cluster_dir=config.Global.cluster_dir
331 )
343 )
332 return d
344 return d
333
345
334 def stop_controller(self, r=None):
346 def stop_controller(self, r=None):
335 # log.msg("In stop_controller")
347 # log.msg("In stop_controller")
336 if self.controller_launcher.running:
348 if self.controller_launcher.running:
337 d = self.controller_launcher.stop()
349 d = self.controller_launcher.stop()
338 d.addErrback(self.log_err)
350 d.addErrback(self.log_err)
339 return d
351 return d
340 else:
352 else:
341 return defer.succeed(None)
353 return defer.succeed(None)
342
354
343 def stop_engines(self, r=None):
355 def stop_engines(self, r=None):
344 # log.msg("In stop_engines")
356 # log.msg("In stop_engines")
345 if self.engine_launcher.running:
357 if self.engine_launcher.running:
346 d = self.engine_launcher.stop()
358 d = self.engine_launcher.stop()
347 d.addErrback(self.log_err)
359 d.addErrback(self.log_err)
348 return d
360 return d
349 else:
361 else:
350 return defer.succeed(None)
362 return defer.succeed(None)
351
363
352 def log_err(self, f):
364 def log_err(self, f):
353 log.msg(f.getTraceback())
365 log.msg(f.getTraceback())
354 return None
366 return None
355
367
356 def stop_launchers(self, r=None):
368 def stop_launchers(self, r=None):
357 if not self._stopping:
369 if not self._stopping:
358 self._stopping = True
370 self._stopping = True
359 if isinstance(r, failure.Failure):
371 if isinstance(r, failure.Failure):
360 log.msg('Unexpected error in ipcluster:')
372 log.msg('Unexpected error in ipcluster:')
361 log.msg(r.getTraceback())
373 log.msg(r.getTraceback())
362 log.msg("IPython cluster: stopping")
374 log.msg("IPython cluster: stopping")
363 d= self.stop_engines()
375 d= self.stop_engines()
364 d2 = self.stop_controller()
376 d2 = self.stop_controller()
365 # Wait a few seconds to let things shut down.
377 # Wait a few seconds to let things shut down.
366 reactor.callLater(4.0, reactor.stop)
378 reactor.callLater(4.0, reactor.stop)
367
379
368 def sigint_handler(self, signum, frame):
380 def sigint_handler(self, signum, frame):
369 self.stop_launchers()
381 self.stop_launchers()
370
382
371 def start_logging(self):
383 def start_logging(self):
372 # Remove old log files of the controller and engine
384 # Remove old log files of the controller and engine
373 if self.master_config.Global.clean_logs:
385 if self.master_config.Global.clean_logs:
374 log_dir = self.master_config.Global.log_dir
386 log_dir = self.master_config.Global.log_dir
375 for f in os.listdir(log_dir):
387 for f in os.listdir(log_dir):
376 if f.startswith('ipengine' + '-'):
388 if f.startswith('ipengine' + '-'):
377 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
389 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
378 os.remove(os.path.join(log_dir, f))
390 os.remove(os.path.join(log_dir, f))
379 if f.startswith('ipcontroller' + '-'):
391 if f.startswith('ipcontroller' + '-'):
380 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
392 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
381 os.remove(os.path.join(log_dir, f))
393 os.remove(os.path.join(log_dir, f))
382 # This will remote old log files for ipcluster itself
394 # This will remote old log files for ipcluster itself
383 super(IPClusterApp, self).start_logging()
395 super(IPClusterApp, self).start_logging()
384
396
385 def start_app(self):
397 def start_app(self):
386 """Start the application, depending on what subcommand is used."""
398 """Start the application, depending on what subcommand is used."""
387 subcmd = self.master_config.Global.subcommand
399 subcmd = self.master_config.Global.subcommand
388 if subcmd=='create' or subcmd=='list':
400 if subcmd=='create' or subcmd=='list':
389 return
401 return
390 elif subcmd=='start':
402 elif subcmd=='start':
391 self.start_app_start()
403 self.start_app_start()
392 elif subcmd=='stop':
404 elif subcmd=='stop':
393 self.start_app_stop()
405 self.start_app_stop()
394
406
395 def start_app_start(self):
407 def start_app_start(self):
396 """Start the app for the start subcommand."""
408 """Start the app for the start subcommand."""
397 config = self.master_config
409 config = self.master_config
398 # First see if the cluster is already running
410 # First see if the cluster is already running
399 try:
411 try:
400 pid = self.get_pid_from_file()
412 pid = self.get_pid_from_file()
401 except PIDFileError:
413 except PIDFileError:
402 pass
414 pass
403 else:
415 else:
404 self.log.critical(
416 self.log.critical(
405 'Cluster is already running with [pid=%s]. '
417 'Cluster is already running with [pid=%s]. '
406 'use "ipcluster stop" to stop the cluster.' % pid
418 'use "ipcluster stop" to stop the cluster.' % pid
407 )
419 )
408 # Here I exit with a unusual exit status that other processes
420 # Here I exit with a unusual exit status that other processes
409 # can watch for to learn how I existed.
421 # can watch for to learn how I existed.
410 self.exit(ALREADY_STARTED)
422 self.exit(ALREADY_STARTED)
411
423
412 # Now log and daemonize
424 # Now log and daemonize
413 self.log.info(
425 self.log.info(
414 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
426 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
415 )
427 )
416 # TODO: Get daemonize working on Windows or as a Windows Server.
428 # TODO: Get daemonize working on Windows or as a Windows Server.
417 if config.Global.daemonize:
429 if config.Global.daemonize:
418 if os.name=='posix':
430 if os.name=='posix':
419 daemonize()
431 daemonize()
420
432
421 # Now write the new pid file AFTER our new forked pid is active.
433 # Now write the new pid file AFTER our new forked pid is active.
422 self.write_pid_file()
434 self.write_pid_file()
423 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
435 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
424 reactor.run()
436 reactor.run()
425
437
426 def start_app_stop(self):
438 def start_app_stop(self):
427 """Start the app for the stop subcommand."""
439 """Start the app for the stop subcommand."""
428 config = self.master_config
440 config = self.master_config
429 try:
441 try:
430 pid = self.get_pid_from_file()
442 pid = self.get_pid_from_file()
431 except PIDFileError:
443 except PIDFileError:
432 self.log.critical(
444 self.log.critical(
433 'Problem reading pid file, cluster is probably not running.'
445 'Problem reading pid file, cluster is probably not running.'
434 )
446 )
435 # Here I exit with a unusual exit status that other processes
447 # Here I exit with a unusual exit status that other processes
436 # can watch for to learn how I existed.
448 # can watch for to learn how I existed.
437 self.exit(ALREADY_STOPPED)
449 self.exit(ALREADY_STOPPED)
438 else:
450 else:
439 if os.name=='posix':
451 if os.name=='posix':
440 sig = config.Global.signal
452 sig = config.Global.signal
441 self.log.info(
453 self.log.info(
442 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
454 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
443 )
455 )
444 os.kill(pid, sig)
456 os.kill(pid, sig)
445 elif os.name=='nt':
457 elif os.name=='nt':
446 # As of right now, we don't support daemonize on Windows, so
458 # As of right now, we don't support daemonize on Windows, so
447 # stop will not do anything. Minimally, it should clean up the
459 # stop will not do anything. Minimally, it should clean up the
448 # old .pid files.
460 # old .pid files.
449 self.remove_pid_file()
461 self.remove_pid_file()
450
462
451 def launch_new_instance():
463 def launch_new_instance():
452 """Create and run the IPython cluster."""
464 """Create and run the IPython cluster."""
453 app = IPClusterApp()
465 app = IPClusterApp()
454 app.start()
466 app.start()
455
467
456
468
457 if __name__ == '__main__':
469 if __name__ == '__main__':
458 launch_new_instance()
470 launch_new_instance()
459
471
@@ -1,265 +1,275 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
18 from __future__ import with_statement
19
19
20 import copy
20 import copy
21 import os
21 import os
22 import sys
22 import sys
23
23
24 from twisted.application import service
24 from twisted.application import service
25 from twisted.internet import reactor
25 from twisted.internet import reactor
26 from twisted.python import log
26 from twisted.python import log
27
27
28 from IPython.config.loader import Config, NoConfigDefault
28 from IPython.config.loader import Config, NoConfigDefault
29
29
30 from IPython.kernel.clusterdir import (
30 from IPython.kernel.clusterdir import (
31 ApplicationWithClusterDir,
31 ApplicationWithClusterDir,
32 AppWithClusterDirArgParseConfigLoader
32 AppWithClusterDirArgParseConfigLoader
33 )
33 )
34
34
35 from IPython.core import release
35 from IPython.core import release
36
36
37 from IPython.utils.traitlets import Str, Instance, Unicode
37 from IPython.utils.traitlets import Str, Instance, Unicode
38
38
39 from IPython.kernel import controllerservice
39 from IPython.kernel import controllerservice
40
40
41 from IPython.kernel.fcutil import FCServiceFactory
41 from IPython.kernel.fcutil import FCServiceFactory
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Default interfaces
44 # Default interfaces
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47
47
48 # The default client interfaces for FCClientServiceFactory.interfaces
48 # The default client interfaces for FCClientServiceFactory.interfaces
49 default_client_interfaces = Config()
49 default_client_interfaces = Config()
50 default_client_interfaces.Task.interface_chain = [
50 default_client_interfaces.Task.interface_chain = [
51 'IPython.kernel.task.ITaskController',
51 'IPython.kernel.task.ITaskController',
52 'IPython.kernel.taskfc.IFCTaskController'
52 'IPython.kernel.taskfc.IFCTaskController'
53 ]
53 ]
54
54
55 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
55 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
56
56
57 default_client_interfaces.MultiEngine.interface_chain = [
57 default_client_interfaces.MultiEngine.interface_chain = [
58 'IPython.kernel.multiengine.IMultiEngine',
58 'IPython.kernel.multiengine.IMultiEngine',
59 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
59 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
60 ]
60 ]
61
61
62 default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
62 default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
63
63
64 # Make this a dict we can pass to Config.__init__ for the default
64 # Make this a dict we can pass to Config.__init__ for the default
65 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
65 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
66
66
67
67
68
68
69 # The default engine interfaces for FCEngineServiceFactory.interfaces
69 # The default engine interfaces for FCEngineServiceFactory.interfaces
70 default_engine_interfaces = Config()
70 default_engine_interfaces = Config()
71 default_engine_interfaces.Default.interface_chain = [
71 default_engine_interfaces.Default.interface_chain = [
72 'IPython.kernel.enginefc.IFCControllerBase'
72 'IPython.kernel.enginefc.IFCControllerBase'
73 ]
73 ]
74
74
75 default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
75 default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
76
76
77 # Make this a dict we can pass to Config.__init__ for the default
77 # Make this a dict we can pass to Config.__init__ for the default
78 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
78 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
79
79
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Service factories
82 # Service factories
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85
85
86 class FCClientServiceFactory(FCServiceFactory):
86 class FCClientServiceFactory(FCServiceFactory):
87 """A Foolscap implementation of the client services."""
87 """A Foolscap implementation of the client services."""
88
88
89 cert_file = Unicode(u'ipcontroller-client.pem', config=True)
89 cert_file = Unicode(u'ipcontroller-client.pem', config=True)
90 interfaces = Instance(klass=Config, kw=default_client_interfaces,
90 interfaces = Instance(klass=Config, kw=default_client_interfaces,
91 allow_none=False, config=True)
91 allow_none=False, config=True)
92
92
93
93
94 class FCEngineServiceFactory(FCServiceFactory):
94 class FCEngineServiceFactory(FCServiceFactory):
95 """A Foolscap implementation of the engine services."""
95 """A Foolscap implementation of the engine services."""
96
96
97 cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
97 cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
98 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
98 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
99 allow_none=False, config=True)
99 allow_none=False, config=True)
100
100
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # The main application
103 # The main application
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105
105
106
106
107 cl_args = (
107 cl_args = (
108 # Client config
108 # Client config
109 (('--client-ip',), dict(
109 (('--client-ip',), dict(
110 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
110 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
111 help='The IP address or hostname the controller will listen on for '
111 help='The IP address or hostname the controller will listen on for '
112 'client connections.',
112 'client connections.',
113 metavar='FCClientServiceFactory.ip')
113 metavar='FCClientServiceFactory.ip')
114 ),
114 ),
115 (('--client-port',), dict(
115 (('--client-port',), dict(
116 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
116 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
117 help='The port the controller will listen on for client connections. '
117 help='The port the controller will listen on for client connections. '
118 'The default is to use 0, which will autoselect an open port.',
118 'The default is to use 0, which will autoselect an open port.',
119 metavar='FCClientServiceFactory.port')
119 metavar='FCClientServiceFactory.port')
120 ),
120 ),
121 (('--client-location',), dict(
121 (('--client-location',), dict(
122 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
122 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
123 help='The hostname or IP that clients should connect to. This does '
123 help='The hostname or IP that clients should connect to. This does '
124 'not control which interface the controller listens on. Instead, this '
124 'not control which interface the controller listens on. Instead, this '
125 'determines the hostname/IP that is listed in the FURL, which is how '
125 'determines the hostname/IP that is listed in the FURL, which is how '
126 'clients know where to connect. Useful if the controller is listening '
126 'clients know where to connect. Useful if the controller is listening '
127 'on multiple interfaces.',
127 'on multiple interfaces.',
128 metavar='FCClientServiceFactory.location')
128 metavar='FCClientServiceFactory.location')
129 ),
129 ),
130 # Engine config
130 # Engine config
131 (('--engine-ip',), dict(
131 (('--engine-ip',), dict(
132 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
132 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
133 help='The IP address or hostname the controller will listen on for '
133 help='The IP address or hostname the controller will listen on for '
134 'engine connections.',
134 'engine connections.',
135 metavar='FCEngineServiceFactory.ip')
135 metavar='FCEngineServiceFactory.ip')
136 ),
136 ),
137 (('--engine-port',), dict(
137 (('--engine-port',), dict(
138 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
138 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
139 help='The port the controller will listen on for engine connections. '
139 help='The port the controller will listen on for engine connections. '
140 'The default is to use 0, which will autoselect an open port.',
140 'The default is to use 0, which will autoselect an open port.',
141 metavar='FCEngineServiceFactory.port')
141 metavar='FCEngineServiceFactory.port')
142 ),
142 ),
143 (('--engine-location',), dict(
143 (('--engine-location',), dict(
144 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
144 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
145 help='The hostname or IP that engines should connect to. This does '
145 help='The hostname or IP that engines should connect to. This does '
146 'not control which interface the controller listens on. Instead, this '
146 'not control which interface the controller listens on. Instead, this '
147 'determines the hostname/IP that is listed in the FURL, which is how '
147 'determines the hostname/IP that is listed in the FURL, which is how '
148 'engines know where to connect. Useful if the controller is listening '
148 'engines know where to connect. Useful if the controller is listening '
149 'on multiple interfaces.',
149 'on multiple interfaces.',
150 metavar='FCEngineServiceFactory.location')
150 metavar='FCEngineServiceFactory.location')
151 ),
151 ),
152 # Global config
152 # Global config
153 (('--log-to-file',), dict(
153 (('--log-to-file',), dict(
154 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
154 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
155 help='Log to a file in the log directory (default is stdout)')
155 help='Log to a file in the log directory (default is stdout)')
156 ),
156 ),
157 (('-r','--reuse-furls'), dict(
157 (('-r','--reuse-furls'), dict(
158 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
158 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
159 help='Try to reuse all FURL files. If this is not set all FURL files '
159 help='Try to reuse all FURL files. If this is not set all FURL files '
160 'are deleted before the controller starts. This must be set if '
160 'are deleted before the controller starts. This must be set if '
161 'specific ports are specified by --engine-port or --client-port.')
161 'specific ports are specified by --engine-port or --client-port.')
162 ),
162 ),
163 (('--no-secure',), dict(
163 (('--no-secure',), dict(
164 action='store_false', dest='Global.secure', default=NoConfigDefault,
164 action='store_false', dest='Global.secure', default=NoConfigDefault,
165 help='Turn off SSL encryption for all connections.')
165 help='Turn off SSL encryption for all connections.')
166 ),
166 ),
167 (('--secure',), dict(
167 (('--secure',), dict(
168 action='store_true', dest='Global.secure', default=NoConfigDefault,
168 action='store_true', dest='Global.secure', default=NoConfigDefault,
169 help='Turn off SSL encryption for all connections.')
169 help='Turn off SSL encryption for all connections.')
170 )
170 )
171 )
171 )
172
172
173
173
174 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
174 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
175
175
176 arguments = cl_args
176 arguments = cl_args
177
177
178
178
179 _description = """Start the IPython controller for parallel computing.
180
181 The IPython controller provides a gateway between the IPython engines and
182 clients. The controller needs to be started before the engines and can be
183 configured using command line options or using a cluster directory. Cluster
184 directories contain config, log and security files and are usually located in
185 your .ipython directory and named as "cluster_<profile>". See the --profile
186 and --cluster-dir options for details.
187 """
188
179 default_config_file_name = u'ipcontroller_config.py'
189 default_config_file_name = u'ipcontroller_config.py'
180
190
181
191
182 class IPControllerApp(ApplicationWithClusterDir):
192 class IPControllerApp(ApplicationWithClusterDir):
183
193
184 name = u'ipcontroller'
194 name = u'ipcontroller'
185 description = 'Start the IPython controller for parallel computing.'
195 description = _description
186 config_file_name = default_config_file_name
196 config_file_name = default_config_file_name
187 auto_create_cluster_dir = True
197 auto_create_cluster_dir = True
188
198
189 def create_default_config(self):
199 def create_default_config(self):
190 super(IPControllerApp, self).create_default_config()
200 super(IPControllerApp, self).create_default_config()
191 self.default_config.Global.reuse_furls = False
201 self.default_config.Global.reuse_furls = False
192 self.default_config.Global.secure = True
202 self.default_config.Global.secure = True
193 self.default_config.Global.import_statements = []
203 self.default_config.Global.import_statements = []
194 self.default_config.Global.clean_logs = True
204 self.default_config.Global.clean_logs = True
195
205
196 def create_command_line_config(self):
206 def create_command_line_config(self):
197 """Create and return a command line config loader."""
207 """Create and return a command line config loader."""
198 return IPControllerAppCLConfigLoader(
208 return IPControllerAppCLConfigLoader(
199 description=self.description,
209 description=self.description,
200 version=release.version
210 version=release.version
201 )
211 )
202
212
203 def post_load_command_line_config(self):
213 def post_load_command_line_config(self):
204 # Now setup reuse_furls
214 # Now setup reuse_furls
205 c = self.command_line_config
215 c = self.command_line_config
206 if hasattr(c.Global, 'reuse_furls'):
216 if hasattr(c.Global, 'reuse_furls'):
207 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
217 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
208 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
218 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
209 del c.Global.reuse_furls
219 del c.Global.reuse_furls
210 if hasattr(c.Global, 'secure'):
220 if hasattr(c.Global, 'secure'):
211 c.FCClientServiceFactory.secure = c.Global.secure
221 c.FCClientServiceFactory.secure = c.Global.secure
212 c.FCEngineServiceFactory.secure = c.Global.secure
222 c.FCEngineServiceFactory.secure = c.Global.secure
213 del c.Global.secure
223 del c.Global.secure
214
224
215 def construct(self):
225 def construct(self):
216 # This is the working dir by now.
226 # This is the working dir by now.
217 sys.path.insert(0, '')
227 sys.path.insert(0, '')
218
228
219 self.start_logging()
229 self.start_logging()
220 self.import_statements()
230 self.import_statements()
221
231
222 # Create the service hierarchy
232 # Create the service hierarchy
223 self.main_service = service.MultiService()
233 self.main_service = service.MultiService()
224 # The controller service
234 # The controller service
225 controller_service = controllerservice.ControllerService()
235 controller_service = controllerservice.ControllerService()
226 controller_service.setServiceParent(self.main_service)
236 controller_service.setServiceParent(self.main_service)
227 # The client tub and all its refereceables
237 # The client tub and all its refereceables
228 csfactory = FCClientServiceFactory(self.master_config, controller_service)
238 csfactory = FCClientServiceFactory(self.master_config, controller_service)
229 client_service = csfactory.create()
239 client_service = csfactory.create()
230 client_service.setServiceParent(self.main_service)
240 client_service.setServiceParent(self.main_service)
231 # The engine tub
241 # The engine tub
232 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
242 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
233 engine_service = esfactory.create()
243 engine_service = esfactory.create()
234 engine_service.setServiceParent(self.main_service)
244 engine_service.setServiceParent(self.main_service)
235
245
236 def import_statements(self):
246 def import_statements(self):
237 statements = self.master_config.Global.import_statements
247 statements = self.master_config.Global.import_statements
238 for s in statements:
248 for s in statements:
239 try:
249 try:
240 log.msg("Executing statement: '%s'" % s)
250 log.msg("Executing statement: '%s'" % s)
241 exec s in globals(), locals()
251 exec s in globals(), locals()
242 except:
252 except:
243 log.msg("Error running statement: %s" % s)
253 log.msg("Error running statement: %s" % s)
244
254
245 def start_app(self):
255 def start_app(self):
246 # Start the controller service.
256 # Start the controller service.
247 self.main_service.startService()
257 self.main_service.startService()
248 # Write the .pid file overwriting old ones. This allow multiple
258 # Write the .pid file overwriting old ones. This allow multiple
249 # controllers to clober each other. But Windows is not cleaning
259 # controllers to clober each other. But Windows is not cleaning
250 # these up properly.
260 # these up properly.
251 self.write_pid_file(overwrite=True)
261 self.write_pid_file(overwrite=True)
252 # Add a trigger to delete the .pid file upon shutting down.
262 # Add a trigger to delete the .pid file upon shutting down.
253 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
263 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
254 reactor.run()
264 reactor.run()
255
265
256
266
257 def launch_new_instance():
267 def launch_new_instance():
258 """Create and run the IPython controller"""
268 """Create and run the IPython controller"""
259 app = IPControllerApp()
269 app = IPControllerApp()
260 app.start()
270 app.start()
261
271
262
272
263 if __name__ == '__main__':
273 if __name__ == '__main__':
264 launch_new_instance()
274 launch_new_instance()
265
275
@@ -1,237 +1,248 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application
4 The IPython controller application
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from twisted.application import service
21 from twisted.application import service
22 from twisted.internet import reactor
22 from twisted.internet import reactor
23 from twisted.python import log
23 from twisted.python import log
24
24
25 from IPython.config.loader import NoConfigDefault
25 from IPython.config.loader import NoConfigDefault
26
26
27 from IPython.kernel.clusterdir import (
27 from IPython.kernel.clusterdir import (
28 ApplicationWithClusterDir,
28 ApplicationWithClusterDir,
29 AppWithClusterDirArgParseConfigLoader
29 AppWithClusterDirArgParseConfigLoader
30 )
30 )
31 from IPython.core import release
31 from IPython.core import release
32
32
33 from IPython.utils.importstring import import_item
33 from IPython.utils.importstring import import_item
34
34
35 from IPython.kernel.engineservice import EngineService
35 from IPython.kernel.engineservice import EngineService
36 from IPython.kernel.fcutil import Tub
36 from IPython.kernel.fcutil import Tub
37 from IPython.kernel.engineconnector import EngineConnector
37 from IPython.kernel.engineconnector import EngineConnector
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # The main application
40 # The main application
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43
43
44 cl_args = (
44 cl_args = (
45 # Controller config
45 # Controller config
46 (('--furl-file',), dict(
46 (('--furl-file',), dict(
47 type=unicode, dest='Global.furl_file', default=NoConfigDefault,
47 type=unicode, dest='Global.furl_file', default=NoConfigDefault,
48 help='The full location of the file containing the FURL of the '
48 help='The full location of the file containing the FURL of the '
49 'controller. If this is not given, the FURL file must be in the '
49 'controller. If this is not given, the FURL file must be in the '
50 'security directory of the cluster directory. This location is '
50 'security directory of the cluster directory. This location is '
51 'resolved using the --profile and --app-dir options.',
51 'resolved using the --profile and --app-dir options.',
52 metavar='Global.furl_file')
52 metavar='Global.furl_file')
53 ),
53 ),
54 # MPI
54 # MPI
55 (('--mpi',), dict(
55 (('--mpi',), dict(
56 type=str, dest='MPI.use', default=NoConfigDefault,
56 type=str, dest='MPI.use', default=NoConfigDefault,
57 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
57 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
58 metavar='MPI.use')
58 metavar='MPI.use')
59 ),
59 ),
60 # Global config
60 # Global config
61 (('--log-to-file',), dict(
61 (('--log-to-file',), dict(
62 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
62 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
63 help='Log to a file in the log directory (default is stdout)')
63 help='Log to a file in the log directory (default is stdout)')
64 )
64 )
65 )
65 )
66
66
67
67
68 class IPEngineAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
68 class IPEngineAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
69
69
70 arguments = cl_args
70 arguments = cl_args
71
71
72
72
73 mpi4py_init = """from mpi4py import MPI as mpi
73 mpi4py_init = """from mpi4py import MPI as mpi
74 mpi.size = mpi.COMM_WORLD.Get_size()
74 mpi.size = mpi.COMM_WORLD.Get_size()
75 mpi.rank = mpi.COMM_WORLD.Get_rank()
75 mpi.rank = mpi.COMM_WORLD.Get_rank()
76 """
76 """
77
77
78 pytrilinos_init = """from PyTrilinos import Epetra
78 pytrilinos_init = """from PyTrilinos import Epetra
79 class SimpleStruct:
79 class SimpleStruct:
80 pass
80 pass
81 mpi = SimpleStruct()
81 mpi = SimpleStruct()
82 mpi.rank = 0
82 mpi.rank = 0
83 mpi.size = 0
83 mpi.size = 0
84 """
84 """
85
85
86
86
87 default_config_file_name = u'ipengine_config.py'
87 default_config_file_name = u'ipengine_config.py'
88
88
89
89
90 _description = """Start an IPython engine for parallel computing.\n\n
91
92 IPython engines run in parallel and perform computations on behalf of a client
93 and controller. A controller needs to be started before the engines. The
94 engine can be configured using command line options or using a cluster
95 directory. Cluster directories contain config, log and security files and are
96 usually located in your .ipython directory and named as "cluster_<profile>".
97 See the --profile and --cluster-dir options for details.
98 """
99
100
90 class IPEngineApp(ApplicationWithClusterDir):
101 class IPEngineApp(ApplicationWithClusterDir):
91
102
92 name = u'ipengine'
103 name = u'ipengine'
93 description = 'Start the IPython engine for parallel computing.'
104 description = _description
94 config_file_name = default_config_file_name
105 config_file_name = default_config_file_name
95 auto_create_cluster_dir = True
106 auto_create_cluster_dir = True
96
107
97 def create_default_config(self):
108 def create_default_config(self):
98 super(IPEngineApp, self).create_default_config()
109 super(IPEngineApp, self).create_default_config()
99
110
100 # The engine should not clean logs as we don't want to remove the
111 # The engine should not clean logs as we don't want to remove the
101 # active log files of other running engines.
112 # active log files of other running engines.
102 self.default_config.Global.clean_logs = False
113 self.default_config.Global.clean_logs = False
103
114
104 # Global config attributes
115 # Global config attributes
105 self.default_config.Global.exec_lines = []
116 self.default_config.Global.exec_lines = []
106 self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
117 self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
107
118
108 # Configuration related to the controller
119 # Configuration related to the controller
109 # This must match the filename (path not included) that the controller
120 # This must match the filename (path not included) that the controller
110 # used for the FURL file.
121 # used for the FURL file.
111 self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl'
122 self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl'
112 # If given, this is the actual location of the controller's FURL file.
123 # If given, this is the actual location of the controller's FURL file.
113 # If not, this is computed using the profile, app_dir and furl_file_name
124 # If not, this is computed using the profile, app_dir and furl_file_name
114 self.default_config.Global.furl_file = u''
125 self.default_config.Global.furl_file = u''
115
126
116 # The max number of connection attemps and the initial delay between
127 # The max number of connection attemps and the initial delay between
117 # those attemps.
128 # those attemps.
118 self.default_config.Global.connect_delay = 0.1
129 self.default_config.Global.connect_delay = 0.1
119 self.default_config.Global.connect_max_tries = 15
130 self.default_config.Global.connect_max_tries = 15
120
131
121 # MPI related config attributes
132 # MPI related config attributes
122 self.default_config.MPI.use = ''
133 self.default_config.MPI.use = ''
123 self.default_config.MPI.mpi4py = mpi4py_init
134 self.default_config.MPI.mpi4py = mpi4py_init
124 self.default_config.MPI.pytrilinos = pytrilinos_init
135 self.default_config.MPI.pytrilinos = pytrilinos_init
125
136
126 def create_command_line_config(self):
137 def create_command_line_config(self):
127 """Create and return a command line config loader."""
138 """Create and return a command line config loader."""
128 return IPEngineAppCLConfigLoader(
139 return IPEngineAppCLConfigLoader(
129 description=self.description,
140 description=self.description,
130 version=release.version
141 version=release.version
131 )
142 )
132
143
133 def post_load_command_line_config(self):
144 def post_load_command_line_config(self):
134 pass
145 pass
135
146
136 def pre_construct(self):
147 def pre_construct(self):
137 super(IPEngineApp, self).pre_construct()
148 super(IPEngineApp, self).pre_construct()
138 self.find_cont_furl_file()
149 self.find_cont_furl_file()
139
150
140 def find_cont_furl_file(self):
151 def find_cont_furl_file(self):
141 """Set the furl file.
152 """Set the furl file.
142
153
143 Here we don't try to actually see if it exists for is valid as that
154 Here we don't try to actually see if it exists for is valid as that
144 is hadled by the connection logic.
155 is hadled by the connection logic.
145 """
156 """
146 config = self.master_config
157 config = self.master_config
147 # Find the actual controller FURL file
158 # Find the actual controller FURL file
148 if not config.Global.furl_file:
159 if not config.Global.furl_file:
149 try_this = os.path.join(
160 try_this = os.path.join(
150 config.Global.cluster_dir,
161 config.Global.cluster_dir,
151 config.Global.security_dir,
162 config.Global.security_dir,
152 config.Global.furl_file_name
163 config.Global.furl_file_name
153 )
164 )
154 config.Global.furl_file = try_this
165 config.Global.furl_file = try_this
155
166
156 def construct(self):
167 def construct(self):
157 # This is the working dir by now.
168 # This is the working dir by now.
158 sys.path.insert(0, '')
169 sys.path.insert(0, '')
159
170
160 self.start_mpi()
171 self.start_mpi()
161 self.start_logging()
172 self.start_logging()
162
173
163 # Create the underlying shell class and EngineService
174 # Create the underlying shell class and EngineService
164 shell_class = import_item(self.master_config.Global.shell_class)
175 shell_class = import_item(self.master_config.Global.shell_class)
165 self.engine_service = EngineService(shell_class, mpi=mpi)
176 self.engine_service = EngineService(shell_class, mpi=mpi)
166
177
167 self.exec_lines()
178 self.exec_lines()
168
179
169 # Create the service hierarchy
180 # Create the service hierarchy
170 self.main_service = service.MultiService()
181 self.main_service = service.MultiService()
171 self.engine_service.setServiceParent(self.main_service)
182 self.engine_service.setServiceParent(self.main_service)
172 self.tub_service = Tub()
183 self.tub_service = Tub()
173 self.tub_service.setServiceParent(self.main_service)
184 self.tub_service.setServiceParent(self.main_service)
174 # This needs to be called before the connection is initiated
185 # This needs to be called before the connection is initiated
175 self.main_service.startService()
186 self.main_service.startService()
176
187
177 # This initiates the connection to the controller and calls
188 # This initiates the connection to the controller and calls
178 # register_engine to tell the controller we are ready to do work
189 # register_engine to tell the controller we are ready to do work
179 self.engine_connector = EngineConnector(self.tub_service)
190 self.engine_connector = EngineConnector(self.tub_service)
180
191
181 log.msg("Using furl file: %s" % self.master_config.Global.furl_file)
192 log.msg("Using furl file: %s" % self.master_config.Global.furl_file)
182
193
183 reactor.callWhenRunning(self.call_connect)
194 reactor.callWhenRunning(self.call_connect)
184
195
185 def call_connect(self):
196 def call_connect(self):
186 d = self.engine_connector.connect_to_controller(
197 d = self.engine_connector.connect_to_controller(
187 self.engine_service,
198 self.engine_service,
188 self.master_config.Global.furl_file,
199 self.master_config.Global.furl_file,
189 self.master_config.Global.connect_delay,
200 self.master_config.Global.connect_delay,
190 self.master_config.Global.connect_max_tries
201 self.master_config.Global.connect_max_tries
191 )
202 )
192
203
193 def handle_error(f):
204 def handle_error(f):
194 log.msg('Error connecting to controller. This usually means that '
205 log.msg('Error connecting to controller. This usually means that '
195 'i) the controller was not started, ii) a firewall was blocking '
206 'i) the controller was not started, ii) a firewall was blocking '
196 'the engine from connecting to the controller or iii) the engine '
207 'the engine from connecting to the controller or iii) the engine '
197 ' was not pointed at the right FURL file:')
208 ' was not pointed at the right FURL file:')
198 log.msg(f.getErrorMessage())
209 log.msg(f.getErrorMessage())
199 reactor.callLater(0.1, reactor.stop)
210 reactor.callLater(0.1, reactor.stop)
200
211
201 d.addErrback(handle_error)
212 d.addErrback(handle_error)
202
213
203 def start_mpi(self):
214 def start_mpi(self):
204 global mpi
215 global mpi
205 mpikey = self.master_config.MPI.use
216 mpikey = self.master_config.MPI.use
206 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
217 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
207 if mpi_import_statement is not None:
218 if mpi_import_statement is not None:
208 try:
219 try:
209 self.log.info("Initializing MPI:")
220 self.log.info("Initializing MPI:")
210 self.log.info(mpi_import_statement)
221 self.log.info(mpi_import_statement)
211 exec mpi_import_statement in globals()
222 exec mpi_import_statement in globals()
212 except:
223 except:
213 mpi = None
224 mpi = None
214 else:
225 else:
215 mpi = None
226 mpi = None
216
227
217 def exec_lines(self):
228 def exec_lines(self):
218 for line in self.master_config.Global.exec_lines:
229 for line in self.master_config.Global.exec_lines:
219 try:
230 try:
220 log.msg("Executing statement: '%s'" % line)
231 log.msg("Executing statement: '%s'" % line)
221 self.engine_service.execute(line)
232 self.engine_service.execute(line)
222 except:
233 except:
223 log.msg("Error executing statement: %s" % line)
234 log.msg("Error executing statement: %s" % line)
224
235
225 def start_app(self):
236 def start_app(self):
226 reactor.run()
237 reactor.run()
227
238
228
239
229 def launch_new_instance():
240 def launch_new_instance():
230 """Create and run the IPython controller"""
241 """Create and run the IPython controller"""
231 app = IPEngineApp()
242 app = IPEngineApp()
232 app.start()
243 app.start()
233
244
234
245
235 if __name__ == '__main__':
246 if __name__ == '__main__':
236 launch_new_instance()
247 launch_new_instance()
237
248
General Comments 0
You need to be logged in to leave comments. Login now