##// END OF EJS Templates
Finishing up help string work.
Brian E. Granger -
Show More
@@ -1,237 +1,238 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import logging
25 25 import os
26 26
27 27 from IPython.config.application import Application, boolean_flag
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags, base_aliases
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.path import get_ipython_dir
33 33 from IPython.utils.traitlets import Unicode, Bool, Dict
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Constants
37 37 #-----------------------------------------------------------------------------
38 38
39 39 create_help = """Create an IPython profile by name
40 40
41 41 Create an ipython profile directory by its name or
42 42 profile directory path. Profile directories contain
43 43 configuration, log and security related files and are named
44 44 using the convention 'profile_<name>'. By default they are
45 45 located in your ipython directory. Once created, you will
46 46 can edit the configuration files in the profile
47 47 directory to configure IPython. Most users will create a
48 48 profile directory by name,
49 49 `ipython profile create myprofile`, which will put the directory
50 50 in `<ipython_dir>/profile_myprofile`.
51 51 """
52 52 list_help = """List available IPython profiles
53 53
54 54 List all available profiles, by profile location, that can
55 55 be found in the current working directly or in the ipython
56 56 directory. Profile directories are named using the convention
57 57 'profile_<profile>'.
58 58 """
59 59 profile_help = """Manage IPython profiles
60 60
61 61 Profile directories contain
62 62 configuration, log and security related files and are named
63 63 using the convention 'profile_<name>'. By default they are
64 64 located in your ipython directory. You can create profiles
65 65 with `ipython profile create <name>`, or see the profiles you
66 66 already have with `ipython profile list`
67 67
68 68 To get started configuring IPython, simply do:
69 69
70 70 $> ipython profile create
71 71
72 72 and IPython will create the default profile in <ipython_dir>/profile_default,
73 73 where you can edit ipython_config.py to start configuring IPython.
74 74
75 75 """
76 76
77 77 _list_examples = "ipython profile list # list all profiles"
78 78
79 79 _create_examples = """
80 ipython profile create foo # create profile foo
81 ipython profile create foo --init # create with default config files
80 ipython profile create foo # create profile foo w/ default config files
81 ipython profile create foo --reset # restage default config files over current
82 ipython profile create foo --parallel # also stage parallel config files
82 83 """
83 84
84 85 _main_examples = """
85 86 ipython profile create -h # show the help string for the create subcommand
86 87 ipython profile list -h # show the help string for the list subcommand
87 88 """
88 89
89 90 #-----------------------------------------------------------------------------
90 91 # Profile Application Class (for `ipython profile` subcommand)
91 92 #-----------------------------------------------------------------------------
92 93
93 94
94 95 class ProfileList(Application):
95 96 name = u'ipython-profile'
96 97 description = list_help
97 98 examples = _list_examples
98 99
99 100 aliases = Dict({
100 101 'ipython-dir' : 'ProfileList.ipython_dir',
101 102 'log-level' : 'Application.log_level',
102 103 })
103 104 flags = Dict(dict(
104 105 debug = ({'Application' : {'log_level' : 0}},
105 106 "Set Application.log_level to 0, maximizing log output."
106 107 )
107 108 ))
108 109
109 110 ipython_dir = Unicode(get_ipython_dir(), config=True,
110 111 help="""
111 112 The name of the IPython directory. This directory is used for logging
112 113 configuration (through profiles), history storage, etc. The default
113 114 is usually $HOME/.ipython. This options can also be specified through
114 115 the environment variable IPYTHON_DIR.
115 116 """
116 117 )
117 118
118 119 def list_profile_dirs(self):
119 120 # Find the search paths
120 121 paths = [os.getcwdu(), self.ipython_dir]
121 122
122 123 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
123 124 for path in paths:
124 125 files = os.listdir(path)
125 126 for f in files:
126 127 full_path = os.path.join(path, f)
127 128 if os.path.isdir(full_path) and f.startswith('profile_'):
128 129 profile = f.split('_',1)[-1]
129 130 start_cmd = 'ipython profile=%s' % profile
130 131 print start_cmd + " ==> " + full_path
131 132
132 133 def start(self):
133 134 self.list_profile_dirs()
134 135
135 136
136 137 create_flags = {}
137 138 create_flags.update(base_flags)
138 139 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
139 140 "reset config files to defaults", "leave existing config files"))
140 141 create_flags.update(boolean_flag('parallel', 'ProfileCreate.parallel',
141 142 "Include parallel computing config files",
142 143 "Don't include parallel computing config files"))
143 144
144 145
145 146 class ProfileCreate(BaseIPythonApplication):
146 147 name = u'ipython-profile'
147 148 description = create_help
148 149 examples = _create_examples
149 150 auto_create = Bool(True, config=False)
150 151
151 152 def _copy_config_files_default(self):
152 153 return True
153 154
154 155 parallel = Bool(False, config=True,
155 156 help="whether to include parallel computing config files")
156 157 def _parallel_changed(self, name, old, new):
157 158 parallel_files = [ 'ipcontroller_config.py',
158 159 'ipengine_config.py',
159 160 'ipcluster_config.py'
160 161 ]
161 162 if new:
162 163 for cf in parallel_files:
163 164 self.config_files.append(cf)
164 165 else:
165 166 for cf in parallel_files:
166 167 if cf in self.config_files:
167 168 self.config_files.remove(cf)
168 169
169 170 def parse_command_line(self, argv):
170 171 super(ProfileCreate, self).parse_command_line(argv)
171 172 # accept positional arg as profile name
172 173 if self.extra_args:
173 174 self.profile = self.extra_args[0]
174 175
175 176 flags = Dict(create_flags)
176 177
177 178 classes = [ProfileDir]
178 179
179 180 def init_config_files(self):
180 181 super(ProfileCreate, self).init_config_files()
181 182 # use local imports, since these classes may import from here
182 183 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
183 184 apps = [TerminalIPythonApp]
184 185 try:
185 186 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
186 187 except Exception:
187 188 # this should be ImportError, but under weird circumstances
188 189 # this might be an AttributeError, or possibly others
189 190 # in any case, nothing should cause the profile creation to crash.
190 191 pass
191 192 else:
192 193 apps.append(IPythonQtConsoleApp)
193 194 if self.parallel:
194 195 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
195 196 from IPython.parallel.apps.ipengineapp import IPEngineApp
196 197 from IPython.parallel.apps.ipclusterapp import IPClusterStart
197 198 from IPython.parallel.apps.iploggerapp import IPLoggerApp
198 199 apps.extend([
199 200 IPControllerApp,
200 201 IPEngineApp,
201 202 IPClusterStart,
202 203 IPLoggerApp,
203 204 ])
204 205 for App in apps:
205 206 app = App()
206 207 app.config.update(self.config)
207 208 app.log = self.log
208 209 app.overwrite = self.overwrite
209 210 app.copy_config_files=True
210 211 app.profile = self.profile
211 212 app.init_profile_dir()
212 213 app.init_config_files()
213 214
214 215 def stage_default_config_file(self):
215 216 pass
216 217
217 218
218 219 class ProfileApp(Application):
219 220 name = u'ipython-profile'
220 221 description = profile_help
221 222 examples = _main_examples
222 223
223 224 subcommands = Dict(dict(
224 225 create = (ProfileCreate, "Create a new profile dir with default config files"),
225 226 list = (ProfileList, "List existing profiles")
226 227 ))
227 228
228 229 def start(self):
229 230 if self.subapp is None:
230 231 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
231 232 print
232 233 self.print_description()
233 234 self.print_subcommands()
234 235 self.exit(1)
235 236 else:
236 237 return self.subapp.start()
237 238
@@ -1,375 +1,378 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2010 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader
34 34 )
35 35 from IPython.config.application import boolean_flag
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.crashhandler import CrashHandler
39 39 from IPython.core.formatters import PlainTextFormatter
40 40 from IPython.core.application import (
41 41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 42 )
43 43 from IPython.core.shellapp import (
44 44 InteractiveShellApp, shell_flags, shell_aliases
45 45 )
46 46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 47 from IPython.lib import inputhook
48 48 from IPython.utils import warn
49 49 from IPython.utils.path import get_ipython_dir, check_for_old_config
50 50 from IPython.utils.traitlets import (
51 51 Bool, Dict, CaselessStrEnum
52 52 )
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Globals, utilities and helpers
56 56 #-----------------------------------------------------------------------------
57 57
58 58 #: The default config file name for this application.
59 59 default_config_file_name = u'ipython_config.py'
60 60
61 61 _examples = """
62 62 ipython --pylab # start in pylab mode
63 63 ipython --pylab=qt # start in pylab mode with the qt4 backend
64 64 ipython --log_level=DEBUG # set logging to DEBUG
65 65 ipython --profile=foo # start with profile foo
66
66 67 ipython qtconsole # start the qtconsole GUI application
67 ipython profile -h # show the help string for the profile subcmd
68 68 ipython qtconsole -h # show the help string for the qtconsole subcmd
69
70 ipython profile create foo # create profile foo w/ default config files
71 ipython profile -h # show the help string for the profile subcmd
69 72 """
70 73
71 74 #-----------------------------------------------------------------------------
72 75 # Crash handler for this application
73 76 #-----------------------------------------------------------------------------
74 77
75 78 class IPAppCrashHandler(CrashHandler):
76 79 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
77 80
78 81 def __init__(self, app):
79 82 contact_name = release.authors['Fernando'][0]
80 83 contact_email = release.authors['Fernando'][1]
81 84 bug_tracker = 'http://github.com/ipython/ipython/issues'
82 85 super(IPAppCrashHandler,self).__init__(
83 86 app, contact_name, contact_email, bug_tracker
84 87 )
85 88
86 89 def make_report(self,traceback):
87 90 """Return a string containing a crash report."""
88 91
89 92 sec_sep = self.section_sep
90 93 # Start with parent report
91 94 report = [super(IPAppCrashHandler, self).make_report(traceback)]
92 95 # Add interactive-specific info we may have
93 96 rpt_add = report.append
94 97 try:
95 98 rpt_add(sec_sep+"History of session input:")
96 99 for line in self.app.shell.user_ns['_ih']:
97 100 rpt_add(line)
98 101 rpt_add('\n*** Last line of input (may not be in above history):\n')
99 102 rpt_add(self.app.shell._last_input_line+'\n')
100 103 except:
101 104 pass
102 105
103 106 return ''.join(report)
104 107
105 108 #-----------------------------------------------------------------------------
106 109 # Aliases and Flags
107 110 #-----------------------------------------------------------------------------
108 111 flags = dict(base_flags)
109 112 flags.update(shell_flags)
110 113 addflag = lambda *args: flags.update(boolean_flag(*args))
111 114 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
112 115 'Turn on auto editing of files with syntax errors.',
113 116 'Turn off auto editing of files with syntax errors.'
114 117 )
115 118 addflag('banner', 'TerminalIPythonApp.display_banner',
116 119 "Display a banner upon starting IPython.",
117 120 "Don't display a banner upon starting IPython."
118 121 )
119 122 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
120 123 """Set to confirm when you try to exit IPython with an EOF (Control-D
121 124 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
122 125 you can force a direct exit without any confirmation.""",
123 126 "Don't prompt the user when exiting."
124 127 )
125 128 addflag('term-title', 'TerminalInteractiveShell.term_title',
126 129 "Enable auto setting the terminal title.",
127 130 "Disable auto setting the terminal title."
128 131 )
129 132 classic_config = Config()
130 133 classic_config.InteractiveShell.cache_size = 0
131 134 classic_config.PlainTextFormatter.pprint = False
132 135 classic_config.InteractiveShell.prompt_in1 = '>>> '
133 136 classic_config.InteractiveShell.prompt_in2 = '... '
134 137 classic_config.InteractiveShell.prompt_out = ''
135 138 classic_config.InteractiveShell.separate_in = ''
136 139 classic_config.InteractiveShell.separate_out = ''
137 140 classic_config.InteractiveShell.separate_out2 = ''
138 141 classic_config.InteractiveShell.colors = 'NoColor'
139 142 classic_config.InteractiveShell.xmode = 'Plain'
140 143
141 144 flags['classic']=(
142 145 classic_config,
143 146 "Gives IPython a similar feel to the classic Python prompt."
144 147 )
145 148 # # log doesn't make so much sense this way anymore
146 149 # paa('--log','-l',
147 150 # action='store_true', dest='InteractiveShell.logstart',
148 151 # help="Start logging to the default log file (./ipython_log.py).")
149 152 #
150 153 # # quick is harder to implement
151 154 flags['quick']=(
152 155 {'TerminalIPythonApp' : {'quick' : True}},
153 156 "Enable quick startup with no config files."
154 157 )
155 158
156 159 flags['i'] = (
157 160 {'TerminalIPythonApp' : {'force_interact' : True}},
158 161 """also works as '-i'
159 162 If running code from the command line, become interactive afterwards."""
160 163 )
161 164 flags['pylab'] = (
162 165 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
163 166 """Pre-load matplotlib and numpy for interactive use with
164 167 the default matplotlib backend."""
165 168 )
166 169
167 170 aliases = dict(base_aliases)
168 171 aliases.update(shell_aliases)
169 172
170 173 # it's possible we don't want short aliases for *all* of these:
171 174 aliases.update(dict(
172 175 gui='TerminalIPythonApp.gui',
173 176 pylab='TerminalIPythonApp.pylab',
174 177 ))
175 178
176 179 #-----------------------------------------------------------------------------
177 180 # Main classes and functions
178 181 #-----------------------------------------------------------------------------
179 182
180 183
181 184 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
182 185 name = u'ipython'
183 186 description = usage.cl_usage
184 187 default_config_file_name = default_config_file_name
185 188 crash_handler_class = IPAppCrashHandler
186 189 examples = _examples
187 190
188 191 flags = Dict(flags)
189 192 aliases = Dict(aliases)
190 193 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
191 194 subcommands = Dict(dict(
192 195 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
193 196 """Launch the IPython Qt Console."""
194 197 ),
195 198 profile = ("IPython.core.profileapp.ProfileApp",
196 199 "Create and manage IPython profiles.")
197 200 ))
198 201
199 202 # *do* autocreate requested profile, but don't create the config file.
200 203 auto_create=Bool(True)
201 204 # configurables
202 205 ignore_old_config=Bool(False, config=True,
203 206 help="Suppress warning messages about legacy config files"
204 207 )
205 208 quick = Bool(False, config=True,
206 209 help="""Start IPython quickly by skipping the loading of config files."""
207 210 )
208 211 def _quick_changed(self, name, old, new):
209 212 if new:
210 213 self.load_config_file = lambda *a, **kw: None
211 214 self.ignore_old_config=True
212 215
213 216 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
214 217 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
215 218 )
216 219 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
217 220 config=True,
218 221 help="""Pre-load matplotlib and numpy for interactive use,
219 222 selecting a particular matplotlib backend and loop integration.
220 223 """
221 224 )
222 225 display_banner = Bool(True, config=True,
223 226 help="Whether to display a banner upon starting IPython."
224 227 )
225 228
226 229 # if there is code of files to run from the cmd line, don't interact
227 230 # unless the --i flag (App.force_interact) is true.
228 231 force_interact = Bool(False, config=True,
229 232 help="""If a command or file is given via the command-line,
230 233 e.g. 'ipython foo.py"""
231 234 )
232 235 def _force_interact_changed(self, name, old, new):
233 236 if new:
234 237 self.interact = True
235 238
236 239 def _file_to_run_changed(self, name, old, new):
237 240 if new and not self.force_interact:
238 241 self.interact = False
239 242 _code_to_run_changed = _file_to_run_changed
240 243
241 244 # internal, not-configurable
242 245 interact=Bool(True)
243 246
244 247
245 248 def parse_command_line(self, argv=None):
246 249 """override to allow old '-pylab' flag with deprecation warning"""
247 250 argv = sys.argv[1:] if argv is None else argv
248 251
249 252 try:
250 253 idx = argv.index('-pylab')
251 254 except ValueError:
252 255 # `-pylab` not given, proceed as normal
253 256 pass
254 257 else:
255 258 # deprecated `-pylab` given,
256 259 # warn and transform into current syntax
257 260 argv = list(argv) # copy, don't clobber
258 261 warn.warn("`-pylab` flag has been deprecated.\n"
259 262 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
260 263 sub = '--pylab'
261 264 if len(argv) > idx+1:
262 265 # check for gui arg, as in '-pylab qt'
263 266 gui = argv[idx+1]
264 267 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
265 268 sub = '--pylab='+gui
266 269 argv.pop(idx+1)
267 270 argv[idx] = sub
268 271
269 272 return super(TerminalIPythonApp, self).parse_command_line(argv)
270 273
271 274 def initialize(self, argv=None):
272 275 """Do actions after construct, but before starting the app."""
273 276 super(TerminalIPythonApp, self).initialize(argv)
274 277 if self.subapp is not None:
275 278 # don't bother initializing further, starting subapp
276 279 return
277 280 if not self.ignore_old_config:
278 281 check_for_old_config(self.ipython_dir)
279 282 # print self.extra_args
280 283 if self.extra_args:
281 284 self.file_to_run = self.extra_args[0]
282 285 # create the shell
283 286 self.init_shell()
284 287 # and draw the banner
285 288 self.init_banner()
286 289 # Now a variety of things that happen after the banner is printed.
287 290 self.init_gui_pylab()
288 291 self.init_extensions()
289 292 self.init_code()
290 293
291 294 def init_shell(self):
292 295 """initialize the InteractiveShell instance"""
293 296 # I am a little hesitant to put these into InteractiveShell itself.
294 297 # But that might be the place for them
295 298 sys.path.insert(0, '')
296 299
297 300 # Create an InteractiveShell instance.
298 301 # shell.display_banner should always be False for the terminal
299 302 # based app, because we call shell.show_banner() by hand below
300 303 # so the banner shows *before* all extension loading stuff.
301 304 self.shell = TerminalInteractiveShell.instance(config=self.config,
302 305 display_banner=False, profile_dir=self.profile_dir,
303 306 ipython_dir=self.ipython_dir)
304 307
305 308 def init_banner(self):
306 309 """optionally display the banner"""
307 310 if self.display_banner and self.interact:
308 311 self.shell.show_banner()
309 312 # Make sure there is a space below the banner.
310 313 if self.log_level <= logging.INFO: print
311 314
312 315
313 316 def init_gui_pylab(self):
314 317 """Enable GUI event loop integration, taking pylab into account."""
315 318 gui = self.gui
316 319
317 320 # Using `pylab` will also require gui activation, though which toolkit
318 321 # to use may be chosen automatically based on mpl configuration.
319 322 if self.pylab:
320 323 activate = self.shell.enable_pylab
321 324 if self.pylab == 'auto':
322 325 gui = None
323 326 else:
324 327 gui = self.pylab
325 328 else:
326 329 # Enable only GUI integration, no pylab
327 330 activate = inputhook.enable_gui
328 331
329 332 if gui or self.pylab:
330 333 try:
331 334 self.log.info("Enabling GUI event loop integration, "
332 335 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
333 336 activate(gui)
334 337 except:
335 338 self.log.warn("Error in enabling GUI event loop integration:")
336 339 self.shell.showtraceback()
337 340
338 341 def start(self):
339 342 if self.subapp is not None:
340 343 return self.subapp.start()
341 344 # perform any prexec steps:
342 345 if self.interact:
343 346 self.log.debug("Starting IPython's mainloop...")
344 347 self.shell.mainloop()
345 348 else:
346 349 self.log.debug("IPython not interactive...")
347 350
348 351
349 352 def load_default_config(ipython_dir=None):
350 353 """Load the default config file from the default ipython_dir.
351 354
352 355 This is useful for embedded shells.
353 356 """
354 357 if ipython_dir is None:
355 358 ipython_dir = get_ipython_dir()
356 359 profile_dir = os.path.join(ipython_dir, 'profile_default')
357 360 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
358 361 try:
359 362 config = cl.load_config()
360 363 except IOError:
361 364 # no config found
362 365 config = Config()
363 366 return config
364 367
365 368
366 369 def launch_new_instance():
367 370 """Create and run a full blown IPython instance"""
368 371 app = TerminalIPythonApp.instance()
369 372 app.initialize()
370 373 app.start()
371 374
372 375
373 376 if __name__ == '__main__':
374 377 launch_new_instance()
375 378
@@ -1,482 +1,484 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import errno
25 25 import logging
26 26 import os
27 27 import re
28 28 import signal
29 29
30 30 from subprocess import check_call, CalledProcessError, PIPE
31 31 import zmq
32 32 from zmq.eventloop import ioloop
33 33
34 34 from IPython.config.application import Application, boolean_flag
35 35 from IPython.config.loader import Config
36 36 from IPython.core.application import BaseIPythonApplication
37 37 from IPython.core.profiledir import ProfileDir
38 38 from IPython.utils.daemonize import daemonize
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.utils.traitlets import (Int, Unicode, Bool, CFloat, Dict, List,
41 41 DottedObjectName)
42 42
43 43 from IPython.parallel.apps.baseapp import (
44 44 BaseParallelApplication,
45 45 PIDFileError,
46 46 base_flags, base_aliases
47 47 )
48 48
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Module level variables
52 52 #-----------------------------------------------------------------------------
53 53
54 54
55 55 default_config_file_name = u'ipcluster_config.py'
56 56
57 57
58 58 _description = """Start an IPython cluster for parallel computing.
59 59
60 60 An IPython cluster consists of 1 controller and 1 or more engines.
61 61 This command automates the startup of these processes using a wide
62 62 range of startup methods (SSH, local processes, PBS, mpiexec,
63 63 Windows HPC Server 2008). To start a cluster with 4 engines on your
64 64 local host simply do 'ipcluster start --n=4'. For more complex usage
65 65 you will typically do 'ipython create mycluster --parallel', then edit
66 66 configuration files, followed by 'ipcluster start --profile=mycluster --n=4'.
67 67 """
68 68
69 69 _main_examples = """
70 ipcluster start --n=4 # start a 4 node cluster on localhost
70 71 ipcluster start -h # show the help string for the start subcmd
72
71 73 ipcluster stop -h # show the help string for the stop subcmd
72 74 ipcluster engines -h # show the help string for the engines subcmd
73 75 """
74 76
75 77 _start_examples = """
76 78 ipython profile create mycluster --parallel # create mycluster profile
77 79 ipcluster start --profile=mycluster --n=4 # start mycluster with 4 nodes
78 80 """
79 81
80 82 _stop_examples = """
81 83 ipcluster stop --profile=mycluster # stop a running cluster by profile name
82 84 """
83 85
84 86 _engines_examples = """
85 87 ipcluster engines --profile=mycluster --n=4 # start 4 engines only
86 88 """
87 89
88 90
89 91 # Exit codes for ipcluster
90 92
91 93 # This will be the exit code if the ipcluster appears to be running because
92 94 # a .pid file exists
93 95 ALREADY_STARTED = 10
94 96
95 97
96 98 # This will be the exit code if ipcluster stop is run, but there is not .pid
97 99 # file to be found.
98 100 ALREADY_STOPPED = 11
99 101
100 102 # This will be the exit code if ipcluster engines is run, but there is not .pid
101 103 # file to be found.
102 104 NO_CLUSTER = 12
103 105
104 106
105 107 #-----------------------------------------------------------------------------
106 108 # Main application
107 109 #-----------------------------------------------------------------------------
108 110 start_help = """Start an IPython cluster for parallel computing
109 111
110 112 Start an ipython cluster by its profile name or cluster
111 113 directory. Cluster directories contain configuration, log and
112 114 security related files and are named using the convention
113 115 'profile_<name>' and should be creating using the 'start'
114 116 subcommand of 'ipcluster'. If your cluster directory is in
115 117 the cwd or the ipython directory, you can simply refer to it
116 using its profile name, 'ipcluster start n=4 profile=<profile>`,
117 otherwise use the 'profile_dir' option.
118 using its profile name, 'ipcluster start --n=4 --profile=<profile>`,
119 otherwise use the 'profile-dir' option.
118 120 """
119 121 stop_help = """Stop a running IPython cluster
120 122
121 123 Stop a running ipython cluster by its profile name or cluster
122 124 directory. Cluster directories are named using the convention
123 125 'profile_<name>'. If your cluster directory is in
124 126 the cwd or the ipython directory, you can simply refer to it
125 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
126 use the 'profile_dir' option.
127 using its profile name, 'ipcluster stop --profile=<profile>`, otherwise
128 use the '--profile-dir' option.
127 129 """
128 130 engines_help = """Start engines connected to an existing IPython cluster
129 131
130 132 Start one or more engines to connect to an existing Cluster
131 133 by profile name or cluster directory.
132 134 Cluster directories contain configuration, log and
133 135 security related files and are named using the convention
134 136 'profile_<name>' and should be creating using the 'start'
135 137 subcommand of 'ipcluster'. If your cluster directory is in
136 138 the cwd or the ipython directory, you can simply refer to it
137 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
138 otherwise use the 'profile_dir' option.
139 using its profile name, 'ipcluster engines --n=4 --profile=<profile>`,
140 otherwise use the 'profile-dir' option.
139 141 """
140 142 stop_aliases = dict(
141 143 signal='IPClusterStop.signal',
142 144 )
143 145 stop_aliases.update(base_aliases)
144 146
145 147 class IPClusterStop(BaseParallelApplication):
146 148 name = u'ipcluster'
147 149 description = stop_help
148 150 examples = _stop_examples
149 151 config_file_name = Unicode(default_config_file_name)
150 152
151 153 signal = Int(signal.SIGINT, config=True,
152 154 help="signal to use for stopping processes.")
153 155
154 156 aliases = Dict(stop_aliases)
155 157
156 158 def start(self):
157 159 """Start the app for the stop subcommand."""
158 160 try:
159 161 pid = self.get_pid_from_file()
160 162 except PIDFileError:
161 163 self.log.critical(
162 164 'Could not read pid file, cluster is probably not running.'
163 165 )
164 166 # Here I exit with a unusual exit status that other processes
165 167 # can watch for to learn how I existed.
166 168 self.remove_pid_file()
167 169 self.exit(ALREADY_STOPPED)
168 170
169 171 if not self.check_pid(pid):
170 172 self.log.critical(
171 173 'Cluster [pid=%r] is not running.' % pid
172 174 )
173 175 self.remove_pid_file()
174 176 # Here I exit with a unusual exit status that other processes
175 177 # can watch for to learn how I existed.
176 178 self.exit(ALREADY_STOPPED)
177 179
178 180 elif os.name=='posix':
179 181 sig = self.signal
180 182 self.log.info(
181 183 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
182 184 )
183 185 try:
184 186 os.kill(pid, sig)
185 187 except OSError:
186 188 self.log.error("Stopping cluster failed, assuming already dead.",
187 189 exc_info=True)
188 190 self.remove_pid_file()
189 191 elif os.name=='nt':
190 192 try:
191 193 # kill the whole tree
192 194 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
193 195 except (CalledProcessError, OSError):
194 196 self.log.error("Stopping cluster failed, assuming already dead.",
195 197 exc_info=True)
196 198 self.remove_pid_file()
197 199
198 200 engine_aliases = {}
199 201 engine_aliases.update(base_aliases)
200 202 engine_aliases.update(dict(
201 203 n='IPClusterEngines.n',
202 204 engines = 'IPClusterEngines.engine_launcher_class',
203 205 daemonize = 'IPClusterEngines.daemonize',
204 206 ))
205 207 engine_flags = {}
206 208 engine_flags.update(base_flags)
207 209
208 210 engine_flags.update(dict(
209 211 daemonize=(
210 212 {'IPClusterEngines' : {'daemonize' : True}},
211 213 """run the cluster into the background (not available on Windows)""",
212 214 )
213 215 ))
214 216 class IPClusterEngines(BaseParallelApplication):
215 217
216 218 name = u'ipcluster'
217 219 description = engines_help
218 220 examples = _engines_examples
219 221 usage = None
220 222 config_file_name = Unicode(default_config_file_name)
221 223 default_log_level = logging.INFO
222 224 classes = List()
223 225 def _classes_default(self):
224 226 from IPython.parallel.apps import launcher
225 227 launchers = launcher.all_launchers
226 228 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
227 229 return [ProfileDir]+eslaunchers
228 230
229 231 n = Int(2, config=True,
230 232 help="The number of engines to start.")
231 233
232 234 engine_launcher_class = DottedObjectName('LocalEngineSetLauncher',
233 235 config=True,
234 236 help="The class for launching a set of Engines."
235 237 )
236 238 daemonize = Bool(False, config=True,
237 239 help="""Daemonize the ipcluster program. This implies --log-to-file.
238 240 Not available on Windows.
239 241 """)
240 242
241 243 def _daemonize_changed(self, name, old, new):
242 244 if new:
243 245 self.log_to_file = True
244 246
245 247 aliases = Dict(engine_aliases)
246 248 flags = Dict(engine_flags)
247 249 _stopping = False
248 250
249 251 def initialize(self, argv=None):
250 252 super(IPClusterEngines, self).initialize(argv)
251 253 self.init_signal()
252 254 self.init_launchers()
253 255
254 256 def init_launchers(self):
255 257 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
256 258 self.engine_launcher.on_stop(lambda r: self.loop.stop())
257 259
258 260 def init_signal(self):
259 261 # Setup signals
260 262 signal.signal(signal.SIGINT, self.sigint_handler)
261 263
262 264 def build_launcher(self, clsname):
263 265 """import and instantiate a Launcher based on importstring"""
264 266 if '.' not in clsname:
265 267 # not a module, presume it's the raw name in apps.launcher
266 268 clsname = 'IPython.parallel.apps.launcher.'+clsname
267 269 # print repr(clsname)
268 270 klass = import_item(clsname)
269 271
270 272 launcher = klass(
271 273 work_dir=self.profile_dir.location, config=self.config, log=self.log
272 274 )
273 275 return launcher
274 276
275 277 def start_engines(self):
276 278 self.log.info("Starting %i engines"%self.n)
277 279 self.engine_launcher.start(
278 280 self.n,
279 281 self.profile_dir.location
280 282 )
281 283
282 284 def stop_engines(self):
283 285 self.log.info("Stopping Engines...")
284 286 if self.engine_launcher.running:
285 287 d = self.engine_launcher.stop()
286 288 return d
287 289 else:
288 290 return None
289 291
290 292 def stop_launchers(self, r=None):
291 293 if not self._stopping:
292 294 self._stopping = True
293 295 self.log.error("IPython cluster: stopping")
294 296 self.stop_engines()
295 297 # Wait a few seconds to let things shut down.
296 298 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
297 299 dc.start()
298 300
299 301 def sigint_handler(self, signum, frame):
300 302 self.log.debug("SIGINT received, stopping launchers...")
301 303 self.stop_launchers()
302 304
303 305 def start_logging(self):
304 306 # Remove old log files of the controller and engine
305 307 if self.clean_logs:
306 308 log_dir = self.profile_dir.log_dir
307 309 for f in os.listdir(log_dir):
308 310 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
309 311 os.remove(os.path.join(log_dir, f))
310 312 # This will remove old log files for ipcluster itself
311 313 # super(IPBaseParallelApplication, self).start_logging()
312 314
313 315 def start(self):
314 316 """Start the app for the engines subcommand."""
315 317 self.log.info("IPython cluster: started")
316 318 # First see if the cluster is already running
317 319
318 320 # Now log and daemonize
319 321 self.log.info(
320 322 'Starting engines with [daemon=%r]' % self.daemonize
321 323 )
322 324 # TODO: Get daemonize working on Windows or as a Windows Server.
323 325 if self.daemonize:
324 326 if os.name=='posix':
325 327 daemonize()
326 328
327 329 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
328 330 dc.start()
329 331 # Now write the new pid file AFTER our new forked pid is active.
330 332 # self.write_pid_file()
331 333 try:
332 334 self.loop.start()
333 335 except KeyboardInterrupt:
334 336 pass
335 337 except zmq.ZMQError as e:
336 338 if e.errno == errno.EINTR:
337 339 pass
338 340 else:
339 341 raise
340 342
341 343 start_aliases = {}
342 344 start_aliases.update(engine_aliases)
343 345 start_aliases.update(dict(
344 346 delay='IPClusterStart.delay',
345 347 controller = 'IPClusterStart.controller_launcher_class',
346 348 ))
347 349 start_aliases['clean-logs'] = 'IPClusterStart.clean_logs'
348 350
349 351 class IPClusterStart(IPClusterEngines):
350 352
351 353 name = u'ipcluster'
352 354 description = start_help
353 355 examples = _start_examples
354 356 default_log_level = logging.INFO
355 357 auto_create = Bool(True, config=True,
356 358 help="whether to create the profile_dir if it doesn't exist")
357 359 classes = List()
358 360 def _classes_default(self,):
359 361 from IPython.parallel.apps import launcher
360 362 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
361 363
362 364 clean_logs = Bool(True, config=True,
363 365 help="whether to cleanup old logs before starting")
364 366
365 367 delay = CFloat(1., config=True,
366 368 help="delay (in s) between starting the controller and the engines")
367 369
368 370 controller_launcher_class = DottedObjectName('LocalControllerLauncher',
369 371 config=True,
370 372 help="The class for launching a Controller."
371 373 )
372 374 reset = Bool(False, config=True,
373 375 help="Whether to reset config files as part of '--create'."
374 376 )
375 377
376 378 # flags = Dict(flags)
377 379 aliases = Dict(start_aliases)
378 380
379 381 def init_launchers(self):
380 382 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
381 383 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
382 384 self.controller_launcher.on_stop(self.stop_launchers)
383 385
384 386 def start_controller(self):
385 387 self.controller_launcher.start(
386 388 self.profile_dir.location
387 389 )
388 390
389 391 def stop_controller(self):
390 392 # self.log.info("In stop_controller")
391 393 if self.controller_launcher and self.controller_launcher.running:
392 394 return self.controller_launcher.stop()
393 395
394 396 def stop_launchers(self, r=None):
395 397 if not self._stopping:
396 398 self.stop_controller()
397 399 super(IPClusterStart, self).stop_launchers()
398 400
399 401 def start(self):
400 402 """Start the app for the start subcommand."""
401 403 # First see if the cluster is already running
402 404 try:
403 405 pid = self.get_pid_from_file()
404 406 except PIDFileError:
405 407 pass
406 408 else:
407 409 if self.check_pid(pid):
408 410 self.log.critical(
409 411 'Cluster is already running with [pid=%s]. '
410 412 'use "ipcluster stop" to stop the cluster.' % pid
411 413 )
412 414 # Here I exit with a unusual exit status that other processes
413 415 # can watch for to learn how I existed.
414 416 self.exit(ALREADY_STARTED)
415 417 else:
416 418 self.remove_pid_file()
417 419
418 420
419 421 # Now log and daemonize
420 422 self.log.info(
421 423 'Starting ipcluster with [daemon=%r]' % self.daemonize
422 424 )
423 425 # TODO: Get daemonize working on Windows or as a Windows Server.
424 426 if self.daemonize:
425 427 if os.name=='posix':
426 428 daemonize()
427 429
428 430 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
429 431 dc.start()
430 432 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
431 433 dc.start()
432 434 # Now write the new pid file AFTER our new forked pid is active.
433 435 self.write_pid_file()
434 436 try:
435 437 self.loop.start()
436 438 except KeyboardInterrupt:
437 439 pass
438 440 except zmq.ZMQError as e:
439 441 if e.errno == errno.EINTR:
440 442 pass
441 443 else:
442 444 raise
443 445 finally:
444 446 self.remove_pid_file()
445 447
446 448 base='IPython.parallel.apps.ipclusterapp.IPCluster'
447 449
448 450 class IPClusterApp(Application):
449 451 name = u'ipcluster'
450 452 description = _description
451 453 examples = _main_examples
452 454
453 455 subcommands = {
454 456 'start' : (base+'Start', start_help),
455 457 'stop' : (base+'Stop', stop_help),
456 458 'engines' : (base+'Engines', engines_help),
457 459 }
458 460
459 461 # no aliases or flags for parent App
460 462 aliases = Dict()
461 463 flags = Dict()
462 464
463 465 def start(self):
464 466 if self.subapp is None:
465 467 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
466 468 print
467 469 self.print_description()
468 470 self.print_subcommands()
469 471 self.exit(1)
470 472 else:
471 473 return self.subapp.start()
472 474
473 475 def launch_new_instance():
474 476 """Create and run the IPython cluster."""
475 477 app = IPClusterApp.instance()
476 478 app.initialize()
477 479 app.start()
478 480
479 481
480 482 if __name__ == '__main__':
481 483 launch_new_instance()
482 484
@@ -1,425 +1,425 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython controller application.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 from __future__ import with_statement
25 25
26 26 import os
27 27 import socket
28 28 import stat
29 29 import sys
30 30 import uuid
31 31
32 32 from multiprocessing import Process
33 33
34 34 import zmq
35 35 from zmq.devices import ProcessMonitoredQueue
36 36 from zmq.log.handlers import PUBHandler
37 37 from zmq.utils import jsonapi as json
38 38
39 39 from IPython.config.application import boolean_flag
40 40 from IPython.core.profiledir import ProfileDir
41 41
42 42 from IPython.parallel.apps.baseapp import (
43 43 BaseParallelApplication,
44 44 base_aliases,
45 45 base_flags,
46 46 )
47 47 from IPython.utils.importstring import import_item
48 48 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict
49 49
50 50 # from IPython.parallel.controller.controller import ControllerFactory
51 51 from IPython.zmq.session import Session
52 52 from IPython.parallel.controller.heartmonitor import HeartMonitor
53 53 from IPython.parallel.controller.hub import HubFactory
54 54 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
55 55 from IPython.parallel.controller.sqlitedb import SQLiteDB
56 56
57 57 from IPython.parallel.util import signal_children, split_url, asbytes
58 58
59 59 # conditional import of MongoDB backend class
60 60
61 61 try:
62 62 from IPython.parallel.controller.mongodb import MongoDB
63 63 except ImportError:
64 64 maybe_mongo = []
65 65 else:
66 66 maybe_mongo = [MongoDB]
67 67
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Module level variables
71 71 #-----------------------------------------------------------------------------
72 72
73 73
74 74 #: The default config file name for this application
75 75 default_config_file_name = u'ipcontroller_config.py'
76 76
77 77
78 78 _description = """Start the IPython controller for parallel computing.
79 79
80 80 The IPython controller provides a gateway between the IPython engines and
81 81 clients. The controller needs to be started before the engines and can be
82 82 configured using command line options or using a cluster directory. Cluster
83 83 directories contain config, log and security files and are usually located in
84 84 your ipython directory and named as "profile_name". See the `profile`
85 and `profile_dir` options for details.
85 and `profile-dir` options for details.
86 86 """
87 87
88 88 _examples = """
89 89 ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines
90 90 ipcontroller --scheme=pure # use the pure zeromq scheduler
91 91 """
92 92
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # The main application
96 96 #-----------------------------------------------------------------------------
97 97 flags = {}
98 98 flags.update(base_flags)
99 99 flags.update({
100 100 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
101 101 'Use threads instead of processes for the schedulers'),
102 102 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
103 103 'use the SQLiteDB backend'),
104 104 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
105 105 'use the MongoDB backend'),
106 106 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
107 107 'use the in-memory DictDB backend'),
108 108 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
109 109 'reuse existing json connection files')
110 110 })
111 111
112 112 flags.update(boolean_flag('secure', 'IPControllerApp.secure',
113 113 "Use HMAC digests for authentication of messages.",
114 114 "Don't authenticate messages."
115 115 ))
116 116 aliases = dict(
117 117 secure = 'IPControllerApp.secure',
118 118 ssh = 'IPControllerApp.ssh_server',
119 119 location = 'IPControllerApp.location',
120 120
121 121 ident = 'Session.session',
122 122 user = 'Session.username',
123 123 keyfile = 'Session.keyfile',
124 124
125 125 url = 'HubFactory.url',
126 126 ip = 'HubFactory.ip',
127 127 transport = 'HubFactory.transport',
128 128 port = 'HubFactory.regport',
129 129
130 130 ping = 'HeartMonitor.period',
131 131
132 132 scheme = 'TaskScheduler.scheme_name',
133 133 hwm = 'TaskScheduler.hwm',
134 134 )
135 135 aliases.update(base_aliases)
136 136
137 137
138 138 class IPControllerApp(BaseParallelApplication):
139 139
140 140 name = u'ipcontroller'
141 141 description = _description
142 142 examples = _examples
143 143 config_file_name = Unicode(default_config_file_name)
144 144 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
145 145
146 146 # change default to True
147 147 auto_create = Bool(True, config=True,
148 148 help="""Whether to create profile dir if it doesn't exist.""")
149 149
150 150 reuse_files = Bool(False, config=True,
151 151 help='Whether to reuse existing json connection files.'
152 152 )
153 153 secure = Bool(True, config=True,
154 154 help='Whether to use HMAC digests for extra message authentication.'
155 155 )
156 156 ssh_server = Unicode(u'', config=True,
157 157 help="""ssh url for clients to use when connecting to the Controller
158 158 processes. It should be of the form: [user@]server[:port]. The
159 159 Controller's listening addresses must be accessible from the ssh server""",
160 160 )
161 161 location = Unicode(u'', config=True,
162 162 help="""The external IP or domain name of the Controller, used for disambiguating
163 163 engine and client connections.""",
164 164 )
165 165 import_statements = List([], config=True,
166 166 help="import statements to be run at startup. Necessary in some environments"
167 167 )
168 168
169 169 use_threads = Bool(False, config=True,
170 170 help='Use threads instead of processes for the schedulers',
171 171 )
172 172
173 173 # internal
174 174 children = List()
175 175 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
176 176
177 177 def _use_threads_changed(self, name, old, new):
178 178 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
179 179
180 180 aliases = Dict(aliases)
181 181 flags = Dict(flags)
182 182
183 183
184 184 def save_connection_dict(self, fname, cdict):
185 185 """save a connection dict to json file."""
186 186 c = self.config
187 187 url = cdict['url']
188 188 location = cdict['location']
189 189 if not location:
190 190 try:
191 191 proto,ip,port = split_url(url)
192 192 except AssertionError:
193 193 pass
194 194 else:
195 195 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
196 196 cdict['location'] = location
197 197 fname = os.path.join(self.profile_dir.security_dir, fname)
198 198 with open(fname, 'wb') as f:
199 199 f.write(json.dumps(cdict, indent=2))
200 200 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
201 201
202 202 def load_config_from_json(self):
203 203 """load config from existing json connector files."""
204 204 c = self.config
205 205 # load from engine config
206 206 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-engine.json')) as f:
207 207 cfg = json.loads(f.read())
208 208 key = c.Session.key = asbytes(cfg['exec_key'])
209 209 xport,addr = cfg['url'].split('://')
210 210 c.HubFactory.engine_transport = xport
211 211 ip,ports = addr.split(':')
212 212 c.HubFactory.engine_ip = ip
213 213 c.HubFactory.regport = int(ports)
214 214 self.location = cfg['location']
215 215 # load client config
216 216 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-client.json')) as f:
217 217 cfg = json.loads(f.read())
218 218 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
219 219 xport,addr = cfg['url'].split('://')
220 220 c.HubFactory.client_transport = xport
221 221 ip,ports = addr.split(':')
222 222 c.HubFactory.client_ip = ip
223 223 self.ssh_server = cfg['ssh']
224 224 assert int(ports) == c.HubFactory.regport, "regport mismatch"
225 225
226 226 def init_hub(self):
227 227 c = self.config
228 228
229 229 self.do_import_statements()
230 230 reusing = self.reuse_files
231 231 if reusing:
232 232 try:
233 233 self.load_config_from_json()
234 234 except (AssertionError,IOError):
235 235 reusing=False
236 236 # check again, because reusing may have failed:
237 237 if reusing:
238 238 pass
239 239 elif self.secure:
240 240 key = str(uuid.uuid4())
241 241 # keyfile = os.path.join(self.profile_dir.security_dir, self.exec_key)
242 242 # with open(keyfile, 'w') as f:
243 243 # f.write(key)
244 244 # os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
245 245 c.Session.key = asbytes(key)
246 246 else:
247 247 key = c.Session.key = b''
248 248
249 249 try:
250 250 self.factory = HubFactory(config=c, log=self.log)
251 251 # self.start_logging()
252 252 self.factory.init_hub()
253 253 except:
254 254 self.log.error("Couldn't construct the Controller", exc_info=True)
255 255 self.exit(1)
256 256
257 257 if not reusing:
258 258 # save to new json config files
259 259 f = self.factory
260 260 cdict = {'exec_key' : key,
261 261 'ssh' : self.ssh_server,
262 262 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
263 263 'location' : self.location
264 264 }
265 265 self.save_connection_dict('ipcontroller-client.json', cdict)
266 266 edict = cdict
267 267 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
268 268 self.save_connection_dict('ipcontroller-engine.json', edict)
269 269
270 270 #
271 271 def init_schedulers(self):
272 272 children = self.children
273 273 mq = import_item(str(self.mq_class))
274 274
275 275 hub = self.factory
276 276 # maybe_inproc = 'inproc://monitor' if self.use_threads else self.monitor_url
277 277 # IOPub relay (in a Process)
278 278 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
279 279 q.bind_in(hub.client_info['iopub'])
280 280 q.bind_out(hub.engine_info['iopub'])
281 281 q.setsockopt_out(zmq.SUBSCRIBE, b'')
282 282 q.connect_mon(hub.monitor_url)
283 283 q.daemon=True
284 284 children.append(q)
285 285
286 286 # Multiplexer Queue (in a Process)
287 287 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'in', b'out')
288 288 q.bind_in(hub.client_info['mux'])
289 289 q.setsockopt_in(zmq.IDENTITY, b'mux')
290 290 q.bind_out(hub.engine_info['mux'])
291 291 q.connect_mon(hub.monitor_url)
292 292 q.daemon=True
293 293 children.append(q)
294 294
295 295 # Control Queue (in a Process)
296 296 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'incontrol', b'outcontrol')
297 297 q.bind_in(hub.client_info['control'])
298 298 q.setsockopt_in(zmq.IDENTITY, b'control')
299 299 q.bind_out(hub.engine_info['control'])
300 300 q.connect_mon(hub.monitor_url)
301 301 q.daemon=True
302 302 children.append(q)
303 303 try:
304 304 scheme = self.config.TaskScheduler.scheme_name
305 305 except AttributeError:
306 306 scheme = TaskScheduler.scheme_name.get_default_value()
307 307 # Task Queue (in a Process)
308 308 if scheme == 'pure':
309 309 self.log.warn("task::using pure XREQ Task scheduler")
310 310 q = mq(zmq.XREP, zmq.XREQ, zmq.PUB, b'intask', b'outtask')
311 311 # q.setsockopt_out(zmq.HWM, hub.hwm)
312 312 q.bind_in(hub.client_info['task'][1])
313 313 q.setsockopt_in(zmq.IDENTITY, b'task')
314 314 q.bind_out(hub.engine_info['task'])
315 315 q.connect_mon(hub.monitor_url)
316 316 q.daemon=True
317 317 children.append(q)
318 318 elif scheme == 'none':
319 319 self.log.warn("task::using no Task scheduler")
320 320
321 321 else:
322 322 self.log.info("task::using Python %s Task scheduler"%scheme)
323 323 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
324 324 hub.monitor_url, hub.client_info['notification'])
325 325 kwargs = dict(logname='scheduler', loglevel=self.log_level,
326 326 log_url = self.log_url, config=dict(self.config))
327 327 if 'Process' in self.mq_class:
328 328 # run the Python scheduler in a Process
329 329 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
330 330 q.daemon=True
331 331 children.append(q)
332 332 else:
333 333 # single-threaded Controller
334 334 kwargs['in_thread'] = True
335 335 launch_scheduler(*sargs, **kwargs)
336 336
337 337
338 338 def save_urls(self):
339 339 """save the registration urls to files."""
340 340 c = self.config
341 341
342 342 sec_dir = self.profile_dir.security_dir
343 343 cf = self.factory
344 344
345 345 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
346 346 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
347 347
348 348 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
349 349 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
350 350
351 351
352 352 def do_import_statements(self):
353 353 statements = self.import_statements
354 354 for s in statements:
355 355 try:
356 356 self.log.msg("Executing statement: '%s'" % s)
357 357 exec s in globals(), locals()
358 358 except:
359 359 self.log.msg("Error running statement: %s" % s)
360 360
361 361 def forward_logging(self):
362 362 if self.log_url:
363 363 self.log.info("Forwarding logging to %s"%self.log_url)
364 364 context = zmq.Context.instance()
365 365 lsock = context.socket(zmq.PUB)
366 366 lsock.connect(self.log_url)
367 367 handler = PUBHandler(lsock)
368 368 self.log.removeHandler(self._log_handler)
369 369 handler.root_topic = 'controller'
370 370 handler.setLevel(self.log_level)
371 371 self.log.addHandler(handler)
372 372 self._log_handler = handler
373 373 # #
374 374
375 375 def initialize(self, argv=None):
376 376 super(IPControllerApp, self).initialize(argv)
377 377 self.forward_logging()
378 378 self.init_hub()
379 379 self.init_schedulers()
380 380
381 381 def start(self):
382 382 # Start the subprocesses:
383 383 self.factory.start()
384 384 child_procs = []
385 385 for child in self.children:
386 386 child.start()
387 387 if isinstance(child, ProcessMonitoredQueue):
388 388 child_procs.append(child.launcher)
389 389 elif isinstance(child, Process):
390 390 child_procs.append(child)
391 391 if child_procs:
392 392 signal_children(child_procs)
393 393
394 394 self.write_pid_file(overwrite=True)
395 395
396 396 try:
397 397 self.factory.loop.start()
398 398 except KeyboardInterrupt:
399 399 self.log.critical("Interrupted, Exiting...\n")
400 400
401 401
402 402
403 403 def launch_new_instance():
404 404 """Create and run the IPython controller"""
405 405 if sys.platform == 'win32':
406 406 # make sure we don't get called from a multiprocessing subprocess
407 407 # this can result in infinite Controllers being started on Windows
408 408 # which doesn't have a proper fork, so multiprocessing is wonky
409 409
410 410 # this only comes up when IPython has been installed using vanilla
411 411 # setuptools, and *not* distribute.
412 412 import multiprocessing
413 413 p = multiprocessing.current_process()
414 414 # the main process has name 'MainProcess'
415 415 # subprocesses will have names like 'Process-1'
416 416 if p.name != 'MainProcess':
417 417 # we are a subprocess, don't start another Controller!
418 418 return
419 419 app = IPControllerApp.instance()
420 420 app.initialize()
421 421 app.start()
422 422
423 423
424 424 if __name__ == '__main__':
425 425 launch_new_instance()
@@ -1,307 +1,307 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython engine application
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import json
25 25 import os
26 26 import sys
27 27 import time
28 28
29 29 import zmq
30 30 from zmq.eventloop import ioloop
31 31
32 32 from IPython.core.profiledir import ProfileDir
33 33 from IPython.parallel.apps.baseapp import (
34 34 BaseParallelApplication,
35 35 base_aliases,
36 36 base_flags,
37 37 )
38 38 from IPython.zmq.log import EnginePUBHandler
39 39
40 40 from IPython.config.configurable import Configurable
41 41 from IPython.zmq.session import Session
42 42 from IPython.parallel.engine.engine import EngineFactory
43 43 from IPython.parallel.engine.streamkernel import Kernel
44 44 from IPython.parallel.util import disambiguate_url, asbytes
45 45
46 46 from IPython.utils.importstring import import_item
47 47 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float
48 48
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Module level variables
52 52 #-----------------------------------------------------------------------------
53 53
54 54 #: The default config file name for this application
55 55 default_config_file_name = u'ipengine_config.py'
56 56
57 57 _description = """Start an IPython engine for parallel computing.
58 58
59 59 IPython engines run in parallel and perform computations on behalf of a client
60 60 and controller. A controller needs to be started before the engines. The
61 61 engine can be configured using command line options or using a cluster
62 62 directory. Cluster directories contain config, log and security files and are
63 63 usually located in your ipython directory and named as "profile_name".
64 See the `profile` and `profile_dir` options for details.
64 See the `profile` and `profile-dir` options for details.
65 65 """
66 66
67 67 _examples = """
68 68 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
69 ipengine --log-to-file --log_level=DEBUG # log to a file with DEBUG verbosity
69 ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
70 70 """
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # MPI configuration
74 74 #-----------------------------------------------------------------------------
75 75
76 76 mpi4py_init = """from mpi4py import MPI as mpi
77 77 mpi.size = mpi.COMM_WORLD.Get_size()
78 78 mpi.rank = mpi.COMM_WORLD.Get_rank()
79 79 """
80 80
81 81
82 82 pytrilinos_init = """from PyTrilinos import Epetra
83 83 class SimpleStruct:
84 84 pass
85 85 mpi = SimpleStruct()
86 86 mpi.rank = 0
87 87 mpi.size = 0
88 88 """
89 89
90 90 class MPI(Configurable):
91 91 """Configurable for MPI initialization"""
92 92 use = Unicode('', config=True,
93 93 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
94 94 )
95 95
96 96 def _on_use_changed(self, old, new):
97 97 # load default init script if it's not set
98 98 if not self.init_script:
99 99 self.init_script = self.default_inits.get(new, '')
100 100
101 101 init_script = Unicode('', config=True,
102 102 help="Initialization code for MPI")
103 103
104 104 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
105 105 config=True)
106 106
107 107
108 108 #-----------------------------------------------------------------------------
109 109 # Main application
110 110 #-----------------------------------------------------------------------------
111 111 aliases = dict(
112 112 file = 'IPEngineApp.url_file',
113 113 c = 'IPEngineApp.startup_command',
114 114 s = 'IPEngineApp.startup_script',
115 115
116 116 ident = 'Session.session',
117 117 user = 'Session.username',
118 118 keyfile = 'Session.keyfile',
119 119
120 120 url = 'EngineFactory.url',
121 121 ip = 'EngineFactory.ip',
122 122 transport = 'EngineFactory.transport',
123 123 port = 'EngineFactory.regport',
124 124 location = 'EngineFactory.location',
125 125
126 126 timeout = 'EngineFactory.timeout',
127 127
128 128 mpi = 'MPI.use',
129 129
130 130 )
131 131 aliases.update(base_aliases)
132 132
133 133
134 134 class IPEngineApp(BaseParallelApplication):
135 135
136 136 name = Unicode(u'ipengine')
137 137 description = Unicode(_description)
138 138 examples = _examples
139 139 config_file_name = Unicode(default_config_file_name)
140 140 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
141 141
142 142 startup_script = Unicode(u'', config=True,
143 143 help='specify a script to be run at startup')
144 144 startup_command = Unicode('', config=True,
145 145 help='specify a command to be run at startup')
146 146
147 147 url_file = Unicode(u'', config=True,
148 148 help="""The full location of the file containing the connection information for
149 149 the controller. If this is not given, the file must be in the
150 150 security directory of the cluster directory. This location is
151 151 resolved using the `profile` or `profile_dir` options.""",
152 152 )
153 153 wait_for_url_file = Float(5, config=True,
154 154 help="""The maximum number of seconds to wait for url_file to exist.
155 155 This is useful for batch-systems and shared-filesystems where the
156 156 controller and engine are started at the same time and it
157 157 may take a moment for the controller to write the connector files.""")
158 158
159 159 url_file_name = Unicode(u'ipcontroller-engine.json')
160 160 log_url = Unicode('', config=True,
161 161 help="""The URL for the iploggerapp instance, for forwarding
162 162 logging to a central location.""")
163 163
164 164 aliases = Dict(aliases)
165 165
166 166 # def find_key_file(self):
167 167 # """Set the key file.
168 168 #
169 169 # Here we don't try to actually see if it exists for is valid as that
170 170 # is hadled by the connection logic.
171 171 # """
172 172 # config = self.master_config
173 173 # # Find the actual controller key file
174 174 # if not config.Global.key_file:
175 175 # try_this = os.path.join(
176 176 # config.Global.profile_dir,
177 177 # config.Global.security_dir,
178 178 # config.Global.key_file_name
179 179 # )
180 180 # config.Global.key_file = try_this
181 181
182 182 def find_url_file(self):
183 183 """Set the url file.
184 184
185 185 Here we don't try to actually see if it exists for is valid as that
186 186 is hadled by the connection logic.
187 187 """
188 188 config = self.config
189 189 # Find the actual controller key file
190 190 if not self.url_file:
191 191 self.url_file = os.path.join(
192 192 self.profile_dir.security_dir,
193 193 self.url_file_name
194 194 )
195 195 def init_engine(self):
196 196 # This is the working dir by now.
197 197 sys.path.insert(0, '')
198 198 config = self.config
199 199 # print config
200 200 self.find_url_file()
201 201
202 202 # was the url manually specified?
203 203 keys = set(self.config.EngineFactory.keys())
204 204 keys = keys.union(set(self.config.RegistrationFactory.keys()))
205 205
206 206 if keys.intersection(set(['ip', 'url', 'port'])):
207 207 # Connection info was specified, don't wait for the file
208 208 url_specified = True
209 209 self.wait_for_url_file = 0
210 210 else:
211 211 url_specified = False
212 212
213 213 if self.wait_for_url_file and not os.path.exists(self.url_file):
214 214 self.log.warn("url_file %r not found"%self.url_file)
215 215 self.log.warn("Waiting up to %.1f seconds for it to arrive."%self.wait_for_url_file)
216 216 tic = time.time()
217 217 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
218 218 # wait for url_file to exist, for up to 10 seconds
219 219 time.sleep(0.1)
220 220
221 221 if os.path.exists(self.url_file):
222 222 self.log.info("Loading url_file %r"%self.url_file)
223 223 with open(self.url_file) as f:
224 224 d = json.loads(f.read())
225 225 if d['exec_key']:
226 226 config.Session.key = asbytes(d['exec_key'])
227 227 d['url'] = disambiguate_url(d['url'], d['location'])
228 228 config.EngineFactory.url = d['url']
229 229 config.EngineFactory.location = d['location']
230 230 elif not url_specified:
231 231 self.log.critical("Fatal: url file never arrived: %s"%self.url_file)
232 232 self.exit(1)
233 233
234 234
235 235 try:
236 236 exec_lines = config.Kernel.exec_lines
237 237 except AttributeError:
238 238 config.Kernel.exec_lines = []
239 239 exec_lines = config.Kernel.exec_lines
240 240
241 241 if self.startup_script:
242 242 enc = sys.getfilesystemencoding() or 'utf8'
243 243 cmd="execfile(%r)"%self.startup_script.encode(enc)
244 244 exec_lines.append(cmd)
245 245 if self.startup_command:
246 246 exec_lines.append(self.startup_command)
247 247
248 248 # Create the underlying shell class and Engine
249 249 # shell_class = import_item(self.master_config.Global.shell_class)
250 250 # print self.config
251 251 try:
252 252 self.engine = EngineFactory(config=config, log=self.log)
253 253 except:
254 254 self.log.error("Couldn't start the Engine", exc_info=True)
255 255 self.exit(1)
256 256
257 257 def forward_logging(self):
258 258 if self.log_url:
259 259 self.log.info("Forwarding logging to %s"%self.log_url)
260 260 context = self.engine.context
261 261 lsock = context.socket(zmq.PUB)
262 262 lsock.connect(self.log_url)
263 263 self.log.removeHandler(self._log_handler)
264 264 handler = EnginePUBHandler(self.engine, lsock)
265 265 handler.setLevel(self.log_level)
266 266 self.log.addHandler(handler)
267 267 self._log_handler = handler
268 268 #
269 269 def init_mpi(self):
270 270 global mpi
271 271 self.mpi = MPI(config=self.config)
272 272
273 273 mpi_import_statement = self.mpi.init_script
274 274 if mpi_import_statement:
275 275 try:
276 276 self.log.info("Initializing MPI:")
277 277 self.log.info(mpi_import_statement)
278 278 exec mpi_import_statement in globals()
279 279 except:
280 280 mpi = None
281 281 else:
282 282 mpi = None
283 283
284 284 def initialize(self, argv=None):
285 285 super(IPEngineApp, self).initialize(argv)
286 286 self.init_mpi()
287 287 self.init_engine()
288 288 self.forward_logging()
289 289
290 290 def start(self):
291 291 self.engine.start()
292 292 try:
293 293 self.engine.loop.start()
294 294 except KeyboardInterrupt:
295 295 self.log.critical("Engine Interrupted, shutting down...\n")
296 296
297 297
298 298 def launch_new_instance():
299 299 """Create and run the IPython engine"""
300 300 app = IPEngineApp.instance()
301 301 app.initialize()
302 302 app.start()
303 303
304 304
305 305 if __name__ == '__main__':
306 306 launch_new_instance()
307 307
@@ -1,101 +1,101 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A simple IPython logger application
5 5
6 6 Authors:
7 7
8 8 * MinRK
9 9
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import sys
25 25
26 26 import zmq
27 27
28 28 from IPython.core.profiledir import ProfileDir
29 29 from IPython.utils.traitlets import Bool, Dict, Unicode
30 30
31 31 from IPython.parallel.apps.baseapp import (
32 32 BaseParallelApplication,
33 33 base_aliases
34 34 )
35 35 from IPython.parallel.apps.logwatcher import LogWatcher
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Module level variables
39 39 #-----------------------------------------------------------------------------
40 40
41 41 #: The default config file name for this application
42 42 default_config_file_name = u'iplogger_config.py'
43 43
44 44 _description = """Start an IPython logger for parallel computing.
45 45
46 46 IPython controllers and engines (and your own processes) can broadcast log messages
47 47 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
48 48 logger can be configured using command line options or using a cluster
49 49 directory. Cluster directories contain config, log and security files and are
50 50 usually located in your ipython directory and named as "profile_name".
51 See the `profile` and `profile_dir` options for details.
51 See the `profile` and `profile-dir` options for details.
52 52 """
53 53
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Main application
57 57 #-----------------------------------------------------------------------------
58 58 aliases = {}
59 59 aliases.update(base_aliases)
60 60 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
61 61
62 62 class IPLoggerApp(BaseParallelApplication):
63 63
64 64 name = u'iplogger'
65 65 description = _description
66 66 config_file_name = Unicode(default_config_file_name)
67 67
68 68 classes = [LogWatcher, ProfileDir]
69 69 aliases = Dict(aliases)
70 70
71 71 def initialize(self, argv=None):
72 72 super(IPLoggerApp, self).initialize(argv)
73 73 self.init_watcher()
74 74
75 75 def init_watcher(self):
76 76 try:
77 77 self.watcher = LogWatcher(config=self.config, log=self.log)
78 78 except:
79 79 self.log.error("Couldn't start the LogWatcher", exc_info=True)
80 80 self.exit(1)
81 81 self.log.info("Listening for log messages on %r"%self.watcher.url)
82 82
83 83
84 84 def start(self):
85 85 self.watcher.start()
86 86 try:
87 87 self.watcher.loop.start()
88 88 except KeyboardInterrupt:
89 89 self.log.critical("Logging Interrupted, shutting down...\n")
90 90
91 91
92 92 def launch_new_instance():
93 93 """Create and run the IPython LogWatcher"""
94 94 app = IPLoggerApp.instance()
95 95 app.initialize()
96 96 app.start()
97 97
98 98
99 99 if __name__ == '__main__':
100 100 launch_new_instance()
101 101
@@ -1,320 +1,320 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Job and task components for writing .xml files that the Windows HPC Server
5 5 2008 can use to start jobs.
6 6
7 7 Authors:
8 8
9 9 * Brian Granger
10 10 * MinRK
11 11
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2011 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import os
26 26 import re
27 27 import uuid
28 28
29 29 from xml.etree import ElementTree as ET
30 30
31 31 from IPython.config.configurable import Configurable
32 32 from IPython.utils.traitlets import (
33 33 Unicode, Int, List, Instance,
34 34 Enum, Bool
35 35 )
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Job and Task classes
39 39 #-----------------------------------------------------------------------------
40 40
41 41
42 42 def as_str(value):
43 43 if isinstance(value, str):
44 44 return value
45 45 elif isinstance(value, bool):
46 46 if value:
47 47 return 'true'
48 48 else:
49 49 return 'false'
50 50 elif isinstance(value, (int, float)):
51 51 return repr(value)
52 52 else:
53 53 return value
54 54
55 55
56 56 def indent(elem, level=0):
57 57 i = "\n" + level*" "
58 58 if len(elem):
59 59 if not elem.text or not elem.text.strip():
60 60 elem.text = i + " "
61 61 if not elem.tail or not elem.tail.strip():
62 62 elem.tail = i
63 63 for elem in elem:
64 64 indent(elem, level+1)
65 65 if not elem.tail or not elem.tail.strip():
66 66 elem.tail = i
67 67 else:
68 68 if level and (not elem.tail or not elem.tail.strip()):
69 69 elem.tail = i
70 70
71 71
72 72 def find_username():
73 73 domain = os.environ.get('USERDOMAIN')
74 74 username = os.environ.get('USERNAME','')
75 75 if domain is None:
76 76 return username
77 77 else:
78 78 return '%s\\%s' % (domain, username)
79 79
80 80
81 81 class WinHPCJob(Configurable):
82 82
83 83 job_id = Unicode('')
84 84 job_name = Unicode('MyJob', config=True)
85 85 min_cores = Int(1, config=True)
86 86 max_cores = Int(1, config=True)
87 87 min_sockets = Int(1, config=True)
88 88 max_sockets = Int(1, config=True)
89 89 min_nodes = Int(1, config=True)
90 90 max_nodes = Int(1, config=True)
91 91 unit_type = Unicode("Core", config=True)
92 92 auto_calculate_min = Bool(True, config=True)
93 93 auto_calculate_max = Bool(True, config=True)
94 94 run_until_canceled = Bool(False, config=True)
95 95 is_exclusive = Bool(False, config=True)
96 96 username = Unicode(find_username(), config=True)
97 97 job_type = Unicode('Batch', config=True)
98 98 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
99 99 default_value='Highest', config=True)
100 100 requested_nodes = Unicode('', config=True)
101 101 project = Unicode('IPython', config=True)
102 102 xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/')
103 103 version = Unicode("2.000")
104 104 tasks = List([])
105 105
106 106 @property
107 107 def owner(self):
108 108 return self.username
109 109
110 110 def _write_attr(self, root, attr, key):
111 111 s = as_str(getattr(self, attr, ''))
112 112 if s:
113 113 root.set(key, s)
114 114
115 115 def as_element(self):
116 116 # We have to add _A_ type things to get the right order than
117 117 # the MSFT XML parser expects.
118 118 root = ET.Element('Job')
119 119 self._write_attr(root, 'version', '_A_Version')
120 120 self._write_attr(root, 'job_name', '_B_Name')
121 121 self._write_attr(root, 'unit_type', '_C_UnitType')
122 122 self._write_attr(root, 'min_cores', '_D_MinCores')
123 123 self._write_attr(root, 'max_cores', '_E_MaxCores')
124 124 self._write_attr(root, 'min_sockets', '_F_MinSockets')
125 125 self._write_attr(root, 'max_sockets', '_G_MaxSockets')
126 126 self._write_attr(root, 'min_nodes', '_H_MinNodes')
127 127 self._write_attr(root, 'max_nodes', '_I_MaxNodes')
128 128 self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled')
129 129 self._write_attr(root, 'is_exclusive', '_K_IsExclusive')
130 130 self._write_attr(root, 'username', '_L_UserName')
131 131 self._write_attr(root, 'job_type', '_M_JobType')
132 132 self._write_attr(root, 'priority', '_N_Priority')
133 133 self._write_attr(root, 'requested_nodes', '_O_RequestedNodes')
134 134 self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax')
135 135 self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin')
136 136 self._write_attr(root, 'project', '_R_Project')
137 137 self._write_attr(root, 'owner', '_S_Owner')
138 138 self._write_attr(root, 'xmlns', '_T_xmlns')
139 139 dependencies = ET.SubElement(root, "Dependencies")
140 140 etasks = ET.SubElement(root, "Tasks")
141 141 for t in self.tasks:
142 142 etasks.append(t.as_element())
143 143 return root
144 144
145 145 def tostring(self):
146 146 """Return the string representation of the job description XML."""
147 147 root = self.as_element()
148 148 indent(root)
149 149 txt = ET.tostring(root, encoding="utf-8")
150 150 # Now remove the tokens used to order the attributes.
151 151 txt = re.sub(r'_[A-Z]_','',txt)
152 152 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
153 153 return txt
154 154
155 155 def write(self, filename):
156 156 """Write the XML job description to a file."""
157 157 txt = self.tostring()
158 158 with open(filename, 'w') as f:
159 159 f.write(txt)
160 160
161 161 def add_task(self, task):
162 162 """Add a task to the job.
163 163
164 164 Parameters
165 165 ----------
166 166 task : :class:`WinHPCTask`
167 167 The task object to add.
168 168 """
169 169 self.tasks.append(task)
170 170
171 171
172 172 class WinHPCTask(Configurable):
173 173
174 174 task_id = Unicode('')
175 175 task_name = Unicode('')
176 176 version = Unicode("2.000")
177 177 min_cores = Int(1, config=True)
178 178 max_cores = Int(1, config=True)
179 179 min_sockets = Int(1, config=True)
180 180 max_sockets = Int(1, config=True)
181 181 min_nodes = Int(1, config=True)
182 182 max_nodes = Int(1, config=True)
183 183 unit_type = Unicode("Core", config=True)
184 184 command_line = Unicode('', config=True)
185 185 work_directory = Unicode('', config=True)
186 186 is_rerunnaable = Bool(True, config=True)
187 187 std_out_file_path = Unicode('', config=True)
188 188 std_err_file_path = Unicode('', config=True)
189 189 is_parametric = Bool(False, config=True)
190 190 environment_variables = Instance(dict, args=(), config=True)
191 191
192 192 def _write_attr(self, root, attr, key):
193 193 s = as_str(getattr(self, attr, ''))
194 194 if s:
195 195 root.set(key, s)
196 196
197 197 def as_element(self):
198 198 root = ET.Element('Task')
199 199 self._write_attr(root, 'version', '_A_Version')
200 200 self._write_attr(root, 'task_name', '_B_Name')
201 201 self._write_attr(root, 'min_cores', '_C_MinCores')
202 202 self._write_attr(root, 'max_cores', '_D_MaxCores')
203 203 self._write_attr(root, 'min_sockets', '_E_MinSockets')
204 204 self._write_attr(root, 'max_sockets', '_F_MaxSockets')
205 205 self._write_attr(root, 'min_nodes', '_G_MinNodes')
206 206 self._write_attr(root, 'max_nodes', '_H_MaxNodes')
207 207 self._write_attr(root, 'command_line', '_I_CommandLine')
208 208 self._write_attr(root, 'work_directory', '_J_WorkDirectory')
209 209 self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable')
210 210 self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath')
211 211 self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath')
212 212 self._write_attr(root, 'is_parametric', '_N_IsParametric')
213 213 self._write_attr(root, 'unit_type', '_O_UnitType')
214 214 root.append(self.get_env_vars())
215 215 return root
216 216
217 217 def get_env_vars(self):
218 218 env_vars = ET.Element('EnvironmentVariables')
219 219 for k, v in self.environment_variables.iteritems():
220 220 variable = ET.SubElement(env_vars, "Variable")
221 221 name = ET.SubElement(variable, "Name")
222 222 name.text = k
223 223 value = ET.SubElement(variable, "Value")
224 224 value.text = v
225 225 return env_vars
226 226
227 227
228 228
229 229 # By declaring these, we can configure the controller and engine separately!
230 230
231 231 class IPControllerJob(WinHPCJob):
232 232 job_name = Unicode('IPController', config=False)
233 233 is_exclusive = Bool(False, config=True)
234 234 username = Unicode(find_username(), config=True)
235 235 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
236 236 default_value='Highest', config=True)
237 237 requested_nodes = Unicode('', config=True)
238 238 project = Unicode('IPython', config=True)
239 239
240 240
241 241 class IPEngineSetJob(WinHPCJob):
242 242 job_name = Unicode('IPEngineSet', config=False)
243 243 is_exclusive = Bool(False, config=True)
244 244 username = Unicode(find_username(), config=True)
245 245 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
246 246 default_value='Highest', config=True)
247 247 requested_nodes = Unicode('', config=True)
248 248 project = Unicode('IPython', config=True)
249 249
250 250
251 251 class IPControllerTask(WinHPCTask):
252 252
253 253 task_name = Unicode('IPController', config=True)
254 254 controller_cmd = List(['ipcontroller.exe'], config=True)
255 controller_args = List(['--log-to-file', 'log-level=40'], config=True)
255 controller_args = List(['--log-to-file', '--log-level=40'], config=True)
256 256 # I don't want these to be configurable
257 257 std_out_file_path = Unicode('', config=False)
258 258 std_err_file_path = Unicode('', config=False)
259 259 min_cores = Int(1, config=False)
260 260 max_cores = Int(1, config=False)
261 261 min_sockets = Int(1, config=False)
262 262 max_sockets = Int(1, config=False)
263 263 min_nodes = Int(1, config=False)
264 264 max_nodes = Int(1, config=False)
265 265 unit_type = Unicode("Core", config=False)
266 266 work_directory = Unicode('', config=False)
267 267
268 268 def __init__(self, config=None):
269 269 super(IPControllerTask, self).__init__(config=config)
270 270 the_uuid = uuid.uuid1()
271 271 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
272 272 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
273 273
274 274 @property
275 275 def command_line(self):
276 276 return ' '.join(self.controller_cmd + self.controller_args)
277 277
278 278
279 279 class IPEngineTask(WinHPCTask):
280 280
281 281 task_name = Unicode('IPEngine', config=True)
282 282 engine_cmd = List(['ipengine.exe'], config=True)
283 engine_args = List(['--log-to-file', 'log_level=40'], config=True)
283 engine_args = List(['--log-to-file', '--log-level=40'], config=True)
284 284 # I don't want these to be configurable
285 285 std_out_file_path = Unicode('', config=False)
286 286 std_err_file_path = Unicode('', config=False)
287 287 min_cores = Int(1, config=False)
288 288 max_cores = Int(1, config=False)
289 289 min_sockets = Int(1, config=False)
290 290 max_sockets = Int(1, config=False)
291 291 min_nodes = Int(1, config=False)
292 292 max_nodes = Int(1, config=False)
293 293 unit_type = Unicode("Core", config=False)
294 294 work_directory = Unicode('', config=False)
295 295
296 296 def __init__(self, config=None):
297 297 super(IPEngineTask,self).__init__(config=config)
298 298 the_uuid = uuid.uuid1()
299 299 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
300 300 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
301 301
302 302 @property
303 303 def command_line(self):
304 304 return ' '.join(self.engine_cmd + self.engine_args)
305 305
306 306
307 307 # j = WinHPCJob(None)
308 308 # j.job_name = 'IPCluster'
309 309 # j.username = 'GNET\\bgranger'
310 310 # j.requested_nodes = 'GREEN'
311 311 #
312 312 # t = WinHPCTask(None)
313 313 # t.task_name = 'Controller'
314 314 # t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10"
315 315 # t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default"
316 316 # t.std_out_file_path = 'controller-out.txt'
317 317 # t.std_err_file_path = 'controller-err.txt'
318 318 # t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages"
319 319 # j.add_task(t)
320 320
General Comments 0
You need to be logged in to leave comments. Login now