##// END OF EJS Templates
add check_pid, and handle stale PID info in ipcluster....
MinRK -
Show More
@@ -1,537 +1,566 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython cluster directory
4 The IPython cluster directory
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 os
20 import os
21 import logging
21 import logging
22 import re
22 import re
23 import shutil
23 import shutil
24 import sys
24 import sys
25
25
26 from subprocess import Popen, PIPE
27
26 from IPython.config.loader import PyFileConfigLoader
28 from IPython.config.loader import PyFileConfigLoader
27 from IPython.config.configurable import Configurable
29 from IPython.config.configurable import Configurable
28 from IPython.core.application import Application, BaseAppConfigLoader
30 from IPython.core.application import Application, BaseAppConfigLoader
29 from IPython.core.crashhandler import CrashHandler
31 from IPython.core.crashhandler import CrashHandler
30 from IPython.core import release
32 from IPython.core import release
31 from IPython.utils.path import (
33 from IPython.utils.path import (
32 get_ipython_package_dir,
34 get_ipython_package_dir,
33 expand_path
35 expand_path
34 )
36 )
35 from IPython.utils.traitlets import Unicode
37 from IPython.utils.traitlets import Unicode
36
38
37 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
38 # Module errors
40 # Module errors
39 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
40
42
41 class ClusterDirError(Exception):
43 class ClusterDirError(Exception):
42 pass
44 pass
43
45
44
46
45 class PIDFileError(Exception):
47 class PIDFileError(Exception):
46 pass
48 pass
47
49
48
50
49 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
50 # Class for managing cluster directories
52 # Class for managing cluster directories
51 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
52
54
53 class ClusterDir(Configurable):
55 class ClusterDir(Configurable):
54 """An object to manage the cluster directory and its resources.
56 """An object to manage the cluster directory and its resources.
55
57
56 The cluster directory is used by :command:`ipengine`,
58 The cluster directory is used by :command:`ipengine`,
57 :command:`ipcontroller` and :command:`ipclsuter` to manage the
59 :command:`ipcontroller` and :command:`ipclsuter` to manage the
58 configuration, logging and security of these applications.
60 configuration, logging and security of these applications.
59
61
60 This object knows how to find, create and manage these directories. This
62 This object knows how to find, create and manage these directories. This
61 should be used by any code that want's to handle cluster directories.
63 should be used by any code that want's to handle cluster directories.
62 """
64 """
63
65
64 security_dir_name = Unicode('security')
66 security_dir_name = Unicode('security')
65 log_dir_name = Unicode('log')
67 log_dir_name = Unicode('log')
66 pid_dir_name = Unicode('pid')
68 pid_dir_name = Unicode('pid')
67 security_dir = Unicode(u'')
69 security_dir = Unicode(u'')
68 log_dir = Unicode(u'')
70 log_dir = Unicode(u'')
69 pid_dir = Unicode(u'')
71 pid_dir = Unicode(u'')
70 location = Unicode(u'')
72 location = Unicode(u'')
71
73
72 def __init__(self, location=u''):
74 def __init__(self, location=u''):
73 super(ClusterDir, self).__init__(location=location)
75 super(ClusterDir, self).__init__(location=location)
74
76
75 def _location_changed(self, name, old, new):
77 def _location_changed(self, name, old, new):
76 if not os.path.isdir(new):
78 if not os.path.isdir(new):
77 os.makedirs(new)
79 os.makedirs(new)
78 self.security_dir = os.path.join(new, self.security_dir_name)
80 self.security_dir = os.path.join(new, self.security_dir_name)
79 self.log_dir = os.path.join(new, self.log_dir_name)
81 self.log_dir = os.path.join(new, self.log_dir_name)
80 self.pid_dir = os.path.join(new, self.pid_dir_name)
82 self.pid_dir = os.path.join(new, self.pid_dir_name)
81 self.check_dirs()
83 self.check_dirs()
82
84
83 def _log_dir_changed(self, name, old, new):
85 def _log_dir_changed(self, name, old, new):
84 self.check_log_dir()
86 self.check_log_dir()
85
87
86 def check_log_dir(self):
88 def check_log_dir(self):
87 if not os.path.isdir(self.log_dir):
89 if not os.path.isdir(self.log_dir):
88 os.mkdir(self.log_dir)
90 os.mkdir(self.log_dir)
89
91
90 def _security_dir_changed(self, name, old, new):
92 def _security_dir_changed(self, name, old, new):
91 self.check_security_dir()
93 self.check_security_dir()
92
94
93 def check_security_dir(self):
95 def check_security_dir(self):
94 if not os.path.isdir(self.security_dir):
96 if not os.path.isdir(self.security_dir):
95 os.mkdir(self.security_dir, 0700)
97 os.mkdir(self.security_dir, 0700)
96 os.chmod(self.security_dir, 0700)
98 os.chmod(self.security_dir, 0700)
97
99
98 def _pid_dir_changed(self, name, old, new):
100 def _pid_dir_changed(self, name, old, new):
99 self.check_pid_dir()
101 self.check_pid_dir()
100
102
101 def check_pid_dir(self):
103 def check_pid_dir(self):
102 if not os.path.isdir(self.pid_dir):
104 if not os.path.isdir(self.pid_dir):
103 os.mkdir(self.pid_dir, 0700)
105 os.mkdir(self.pid_dir, 0700)
104 os.chmod(self.pid_dir, 0700)
106 os.chmod(self.pid_dir, 0700)
105
107
106 def check_dirs(self):
108 def check_dirs(self):
107 self.check_security_dir()
109 self.check_security_dir()
108 self.check_log_dir()
110 self.check_log_dir()
109 self.check_pid_dir()
111 self.check_pid_dir()
110
112
111 def load_config_file(self, filename):
113 def load_config_file(self, filename):
112 """Load a config file from the top level of the cluster dir.
114 """Load a config file from the top level of the cluster dir.
113
115
114 Parameters
116 Parameters
115 ----------
117 ----------
116 filename : unicode or str
118 filename : unicode or str
117 The filename only of the config file that must be located in
119 The filename only of the config file that must be located in
118 the top-level of the cluster directory.
120 the top-level of the cluster directory.
119 """
121 """
120 loader = PyFileConfigLoader(filename, self.location)
122 loader = PyFileConfigLoader(filename, self.location)
121 return loader.load_config()
123 return loader.load_config()
122
124
123 def copy_config_file(self, config_file, path=None, overwrite=False):
125 def copy_config_file(self, config_file, path=None, overwrite=False):
124 """Copy a default config file into the active cluster directory.
126 """Copy a default config file into the active cluster directory.
125
127
126 Default configuration files are kept in :mod:`IPython.config.default`.
128 Default configuration files are kept in :mod:`IPython.config.default`.
127 This function moves these from that location to the working cluster
129 This function moves these from that location to the working cluster
128 directory.
130 directory.
129 """
131 """
130 if path is None:
132 if path is None:
131 import IPython.config.default
133 import IPython.config.default
132 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
134 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
133 path = os.path.sep.join(path)
135 path = os.path.sep.join(path)
134 src = os.path.join(path, config_file)
136 src = os.path.join(path, config_file)
135 dst = os.path.join(self.location, config_file)
137 dst = os.path.join(self.location, config_file)
136 if not os.path.isfile(dst) or overwrite:
138 if not os.path.isfile(dst) or overwrite:
137 shutil.copy(src, dst)
139 shutil.copy(src, dst)
138
140
139 def copy_all_config_files(self, path=None, overwrite=False):
141 def copy_all_config_files(self, path=None, overwrite=False):
140 """Copy all config files into the active cluster directory."""
142 """Copy all config files into the active cluster directory."""
141 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
143 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
142 u'ipcluster_config.py']:
144 u'ipcluster_config.py']:
143 self.copy_config_file(f, path=path, overwrite=overwrite)
145 self.copy_config_file(f, path=path, overwrite=overwrite)
144
146
145 @classmethod
147 @classmethod
146 def create_cluster_dir(csl, cluster_dir):
148 def create_cluster_dir(csl, cluster_dir):
147 """Create a new cluster directory given a full path.
149 """Create a new cluster directory given a full path.
148
150
149 Parameters
151 Parameters
150 ----------
152 ----------
151 cluster_dir : str
153 cluster_dir : str
152 The full path to the cluster directory. If it does exist, it will
154 The full path to the cluster directory. If it does exist, it will
153 be used. If not, it will be created.
155 be used. If not, it will be created.
154 """
156 """
155 return ClusterDir(location=cluster_dir)
157 return ClusterDir(location=cluster_dir)
156
158
157 @classmethod
159 @classmethod
158 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
160 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
159 """Create a cluster dir by profile name and path.
161 """Create a cluster dir by profile name and path.
160
162
161 Parameters
163 Parameters
162 ----------
164 ----------
163 path : str
165 path : str
164 The path (directory) to put the cluster directory in.
166 The path (directory) to put the cluster directory in.
165 profile : str
167 profile : str
166 The name of the profile. The name of the cluster directory will
168 The name of the profile. The name of the cluster directory will
167 be "cluster_<profile>".
169 be "cluster_<profile>".
168 """
170 """
169 if not os.path.isdir(path):
171 if not os.path.isdir(path):
170 raise ClusterDirError('Directory not found: %s' % path)
172 raise ClusterDirError('Directory not found: %s' % path)
171 cluster_dir = os.path.join(path, u'cluster_' + profile)
173 cluster_dir = os.path.join(path, u'cluster_' + profile)
172 return ClusterDir(location=cluster_dir)
174 return ClusterDir(location=cluster_dir)
173
175
174 @classmethod
176 @classmethod
175 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
177 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
176 """Find an existing cluster dir by profile name, return its ClusterDir.
178 """Find an existing cluster dir by profile name, return its ClusterDir.
177
179
178 This searches through a sequence of paths for a cluster dir. If it
180 This searches through a sequence of paths for a cluster dir. If it
179 is not found, a :class:`ClusterDirError` exception will be raised.
181 is not found, a :class:`ClusterDirError` exception will be raised.
180
182
181 The search path algorithm is:
183 The search path algorithm is:
182 1. ``os.getcwd()``
184 1. ``os.getcwd()``
183 2. ``ipython_dir``
185 2. ``ipython_dir``
184 3. The directories found in the ":" separated
186 3. The directories found in the ":" separated
185 :env:`IPCLUSTER_DIR_PATH` environment variable.
187 :env:`IPCLUSTER_DIR_PATH` environment variable.
186
188
187 Parameters
189 Parameters
188 ----------
190 ----------
189 ipython_dir : unicode or str
191 ipython_dir : unicode or str
190 The IPython directory to use.
192 The IPython directory to use.
191 profile : unicode or str
193 profile : unicode or str
192 The name of the profile. The name of the cluster directory
194 The name of the profile. The name of the cluster directory
193 will be "cluster_<profile>".
195 will be "cluster_<profile>".
194 """
196 """
195 dirname = u'cluster_' + profile
197 dirname = u'cluster_' + profile
196 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
198 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
197 if cluster_dir_paths:
199 if cluster_dir_paths:
198 cluster_dir_paths = cluster_dir_paths.split(':')
200 cluster_dir_paths = cluster_dir_paths.split(':')
199 else:
201 else:
200 cluster_dir_paths = []
202 cluster_dir_paths = []
201 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
203 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
202 for p in paths:
204 for p in paths:
203 cluster_dir = os.path.join(p, dirname)
205 cluster_dir = os.path.join(p, dirname)
204 if os.path.isdir(cluster_dir):
206 if os.path.isdir(cluster_dir):
205 return ClusterDir(location=cluster_dir)
207 return ClusterDir(location=cluster_dir)
206 else:
208 else:
207 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
209 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
208
210
209 @classmethod
211 @classmethod
210 def find_cluster_dir(cls, cluster_dir):
212 def find_cluster_dir(cls, cluster_dir):
211 """Find/create a cluster dir and return its ClusterDir.
213 """Find/create a cluster dir and return its ClusterDir.
212
214
213 This will create the cluster directory if it doesn't exist.
215 This will create the cluster directory if it doesn't exist.
214
216
215 Parameters
217 Parameters
216 ----------
218 ----------
217 cluster_dir : unicode or str
219 cluster_dir : unicode or str
218 The path of the cluster directory. This is expanded using
220 The path of the cluster directory. This is expanded using
219 :func:`IPython.utils.genutils.expand_path`.
221 :func:`IPython.utils.genutils.expand_path`.
220 """
222 """
221 cluster_dir = expand_path(cluster_dir)
223 cluster_dir = expand_path(cluster_dir)
222 if not os.path.isdir(cluster_dir):
224 if not os.path.isdir(cluster_dir):
223 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
225 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
224 return ClusterDir(location=cluster_dir)
226 return ClusterDir(location=cluster_dir)
225
227
226
228
227 #-----------------------------------------------------------------------------
229 #-----------------------------------------------------------------------------
228 # Command line options
230 # Command line options
229 #-----------------------------------------------------------------------------
231 #-----------------------------------------------------------------------------
230
232
231 class ClusterDirConfigLoader(BaseAppConfigLoader):
233 class ClusterDirConfigLoader(BaseAppConfigLoader):
232
234
233 def _add_cluster_profile(self, parser):
235 def _add_cluster_profile(self, parser):
234 paa = parser.add_argument
236 paa = parser.add_argument
235 paa('-p', '--profile',
237 paa('-p', '--profile',
236 dest='Global.profile',type=unicode,
238 dest='Global.profile',type=unicode,
237 help=
239 help=
238 """The string name of the profile to be used. This determines the name
240 """The string name of the profile to be used. This determines the name
239 of the cluster dir as: cluster_<profile>. The default profile is named
241 of the cluster dir as: cluster_<profile>. The default profile is named
240 'default'. The cluster directory is resolve this way if the
242 'default'. The cluster directory is resolve this way if the
241 --cluster-dir option is not used.""",
243 --cluster-dir option is not used.""",
242 metavar='Global.profile')
244 metavar='Global.profile')
243
245
244 def _add_cluster_dir(self, parser):
246 def _add_cluster_dir(self, parser):
245 paa = parser.add_argument
247 paa = parser.add_argument
246 paa('--cluster-dir',
248 paa('--cluster-dir',
247 dest='Global.cluster_dir',type=unicode,
249 dest='Global.cluster_dir',type=unicode,
248 help="""Set the cluster dir. This overrides the logic used by the
250 help="""Set the cluster dir. This overrides the logic used by the
249 --profile option.""",
251 --profile option.""",
250 metavar='Global.cluster_dir')
252 metavar='Global.cluster_dir')
251
253
252 def _add_work_dir(self, parser):
254 def _add_work_dir(self, parser):
253 paa = parser.add_argument
255 paa = parser.add_argument
254 paa('--work-dir',
256 paa('--work-dir',
255 dest='Global.work_dir',type=unicode,
257 dest='Global.work_dir',type=unicode,
256 help='Set the working dir for the process.',
258 help='Set the working dir for the process.',
257 metavar='Global.work_dir')
259 metavar='Global.work_dir')
258
260
259 def _add_clean_logs(self, parser):
261 def _add_clean_logs(self, parser):
260 paa = parser.add_argument
262 paa = parser.add_argument
261 paa('--clean-logs',
263 paa('--clean-logs',
262 dest='Global.clean_logs', action='store_true',
264 dest='Global.clean_logs', action='store_true',
263 help='Delete old log flies before starting.')
265 help='Delete old log flies before starting.')
264
266
265 def _add_no_clean_logs(self, parser):
267 def _add_no_clean_logs(self, parser):
266 paa = parser.add_argument
268 paa = parser.add_argument
267 paa('--no-clean-logs',
269 paa('--no-clean-logs',
268 dest='Global.clean_logs', action='store_false',
270 dest='Global.clean_logs', action='store_false',
269 help="Don't Delete old log flies before starting.")
271 help="Don't Delete old log flies before starting.")
270
272
271 def _add_arguments(self):
273 def _add_arguments(self):
272 super(ClusterDirConfigLoader, self)._add_arguments()
274 super(ClusterDirConfigLoader, self)._add_arguments()
273 self._add_cluster_profile(self.parser)
275 self._add_cluster_profile(self.parser)
274 self._add_cluster_dir(self.parser)
276 self._add_cluster_dir(self.parser)
275 self._add_work_dir(self.parser)
277 self._add_work_dir(self.parser)
276 self._add_clean_logs(self.parser)
278 self._add_clean_logs(self.parser)
277 self._add_no_clean_logs(self.parser)
279 self._add_no_clean_logs(self.parser)
278
280
279
281
280 #-----------------------------------------------------------------------------
282 #-----------------------------------------------------------------------------
281 # Crash handler for this application
283 # Crash handler for this application
282 #-----------------------------------------------------------------------------
284 #-----------------------------------------------------------------------------
283
285
284
286
285 _message_template = """\
287 _message_template = """\
286 Oops, $self.app_name crashed. We do our best to make it stable, but...
288 Oops, $self.app_name crashed. We do our best to make it stable, but...
287
289
288 A crash report was automatically generated with the following information:
290 A crash report was automatically generated with the following information:
289 - A verbatim copy of the crash traceback.
291 - A verbatim copy of the crash traceback.
290 - Data on your current $self.app_name configuration.
292 - Data on your current $self.app_name configuration.
291
293
292 It was left in the file named:
294 It was left in the file named:
293 \t'$self.crash_report_fname'
295 \t'$self.crash_report_fname'
294 If you can email this file to the developers, the information in it will help
296 If you can email this file to the developers, the information in it will help
295 them in understanding and correcting the problem.
297 them in understanding and correcting the problem.
296
298
297 You can mail it to: $self.contact_name at $self.contact_email
299 You can mail it to: $self.contact_name at $self.contact_email
298 with the subject '$self.app_name Crash Report'.
300 with the subject '$self.app_name Crash Report'.
299
301
300 If you want to do it now, the following command will work (under Unix):
302 If you want to do it now, the following command will work (under Unix):
301 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
303 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
302
304
303 To ensure accurate tracking of this issue, please file a report about it at:
305 To ensure accurate tracking of this issue, please file a report about it at:
304 $self.bug_tracker
306 $self.bug_tracker
305 """
307 """
306
308
307 class ClusterDirCrashHandler(CrashHandler):
309 class ClusterDirCrashHandler(CrashHandler):
308 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
310 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
309
311
310 message_template = _message_template
312 message_template = _message_template
311
313
312 def __init__(self, app):
314 def __init__(self, app):
313 contact_name = release.authors['Brian'][0]
315 contact_name = release.authors['Brian'][0]
314 contact_email = release.authors['Brian'][1]
316 contact_email = release.authors['Brian'][1]
315 bug_tracker = 'http://github.com/ipython/ipython/issues'
317 bug_tracker = 'http://github.com/ipython/ipython/issues'
316 super(ClusterDirCrashHandler,self).__init__(
318 super(ClusterDirCrashHandler,self).__init__(
317 app, contact_name, contact_email, bug_tracker
319 app, contact_name, contact_email, bug_tracker
318 )
320 )
319
321
320
322
321 #-----------------------------------------------------------------------------
323 #-----------------------------------------------------------------------------
322 # Main application
324 # Main application
323 #-----------------------------------------------------------------------------
325 #-----------------------------------------------------------------------------
324
326
325 class ApplicationWithClusterDir(Application):
327 class ApplicationWithClusterDir(Application):
326 """An application that puts everything into a cluster directory.
328 """An application that puts everything into a cluster directory.
327
329
328 Instead of looking for things in the ipython_dir, this type of application
330 Instead of looking for things in the ipython_dir, this type of application
329 will use its own private directory called the "cluster directory"
331 will use its own private directory called the "cluster directory"
330 for things like config files, log files, etc.
332 for things like config files, log files, etc.
331
333
332 The cluster directory is resolved as follows:
334 The cluster directory is resolved as follows:
333
335
334 * If the ``--cluster-dir`` option is given, it is used.
336 * If the ``--cluster-dir`` option is given, it is used.
335 * If ``--cluster-dir`` is not given, the application directory is
337 * If ``--cluster-dir`` is not given, the application directory is
336 resolve using the profile name as ``cluster_<profile>``. The search
338 resolve using the profile name as ``cluster_<profile>``. The search
337 path for this directory is then i) cwd if it is found there
339 path for this directory is then i) cwd if it is found there
338 and ii) in ipython_dir otherwise.
340 and ii) in ipython_dir otherwise.
339
341
340 The config file for the application is to be put in the cluster
342 The config file for the application is to be put in the cluster
341 dir and named the value of the ``config_file_name`` class attribute.
343 dir and named the value of the ``config_file_name`` class attribute.
342 """
344 """
343
345
344 command_line_loader = ClusterDirConfigLoader
346 command_line_loader = ClusterDirConfigLoader
345 crash_handler_class = ClusterDirCrashHandler
347 crash_handler_class = ClusterDirCrashHandler
346 auto_create_cluster_dir = True
348 auto_create_cluster_dir = True
347 # temporarily override default_log_level to INFO
349 # temporarily override default_log_level to INFO
348 default_log_level = logging.INFO
350 default_log_level = logging.INFO
349
351
350 def create_default_config(self):
352 def create_default_config(self):
351 super(ApplicationWithClusterDir, self).create_default_config()
353 super(ApplicationWithClusterDir, self).create_default_config()
352 self.default_config.Global.profile = u'default'
354 self.default_config.Global.profile = u'default'
353 self.default_config.Global.cluster_dir = u''
355 self.default_config.Global.cluster_dir = u''
354 self.default_config.Global.work_dir = os.getcwd()
356 self.default_config.Global.work_dir = os.getcwd()
355 self.default_config.Global.log_to_file = False
357 self.default_config.Global.log_to_file = False
356 self.default_config.Global.log_url = None
358 self.default_config.Global.log_url = None
357 self.default_config.Global.clean_logs = False
359 self.default_config.Global.clean_logs = False
358
360
359 def find_resources(self):
361 def find_resources(self):
360 """This resolves the cluster directory.
362 """This resolves the cluster directory.
361
363
362 This tries to find the cluster directory and if successful, it will
364 This tries to find the cluster directory and if successful, it will
363 have done:
365 have done:
364 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
366 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
365 the application.
367 the application.
366 * Sets ``self.cluster_dir`` attribute of the application and config
368 * Sets ``self.cluster_dir`` attribute of the application and config
367 objects.
369 objects.
368
370
369 The algorithm used for this is as follows:
371 The algorithm used for this is as follows:
370 1. Try ``Global.cluster_dir``.
372 1. Try ``Global.cluster_dir``.
371 2. Try using ``Global.profile``.
373 2. Try using ``Global.profile``.
372 3. If both of these fail and ``self.auto_create_cluster_dir`` is
374 3. If both of these fail and ``self.auto_create_cluster_dir`` is
373 ``True``, then create the new cluster dir in the IPython directory.
375 ``True``, then create the new cluster dir in the IPython directory.
374 4. If all fails, then raise :class:`ClusterDirError`.
376 4. If all fails, then raise :class:`ClusterDirError`.
375 """
377 """
376
378
377 try:
379 try:
378 cluster_dir = self.command_line_config.Global.cluster_dir
380 cluster_dir = self.command_line_config.Global.cluster_dir
379 except AttributeError:
381 except AttributeError:
380 cluster_dir = self.default_config.Global.cluster_dir
382 cluster_dir = self.default_config.Global.cluster_dir
381 cluster_dir = expand_path(cluster_dir)
383 cluster_dir = expand_path(cluster_dir)
382 try:
384 try:
383 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
385 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
384 except ClusterDirError:
386 except ClusterDirError:
385 pass
387 pass
386 else:
388 else:
387 self.log.info('Using existing cluster dir: %s' % \
389 self.log.info('Using existing cluster dir: %s' % \
388 self.cluster_dir_obj.location
390 self.cluster_dir_obj.location
389 )
391 )
390 self.finish_cluster_dir()
392 self.finish_cluster_dir()
391 return
393 return
392
394
393 try:
395 try:
394 self.profile = self.command_line_config.Global.profile
396 self.profile = self.command_line_config.Global.profile
395 except AttributeError:
397 except AttributeError:
396 self.profile = self.default_config.Global.profile
398 self.profile = self.default_config.Global.profile
397 try:
399 try:
398 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
400 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
399 self.ipython_dir, self.profile)
401 self.ipython_dir, self.profile)
400 except ClusterDirError:
402 except ClusterDirError:
401 pass
403 pass
402 else:
404 else:
403 self.log.info('Using existing cluster dir: %s' % \
405 self.log.info('Using existing cluster dir: %s' % \
404 self.cluster_dir_obj.location
406 self.cluster_dir_obj.location
405 )
407 )
406 self.finish_cluster_dir()
408 self.finish_cluster_dir()
407 return
409 return
408
410
409 if self.auto_create_cluster_dir:
411 if self.auto_create_cluster_dir:
410 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
412 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
411 self.ipython_dir, self.profile
413 self.ipython_dir, self.profile
412 )
414 )
413 self.log.info('Creating new cluster dir: %s' % \
415 self.log.info('Creating new cluster dir: %s' % \
414 self.cluster_dir_obj.location
416 self.cluster_dir_obj.location
415 )
417 )
416 self.finish_cluster_dir()
418 self.finish_cluster_dir()
417 else:
419 else:
418 raise ClusterDirError('Could not find a valid cluster directory.')
420 raise ClusterDirError('Could not find a valid cluster directory.')
419
421
420 def finish_cluster_dir(self):
422 def finish_cluster_dir(self):
421 # Set the cluster directory
423 # Set the cluster directory
422 self.cluster_dir = self.cluster_dir_obj.location
424 self.cluster_dir = self.cluster_dir_obj.location
423
425
424 # These have to be set because they could be different from the one
426 # These have to be set because they could be different from the one
425 # that we just computed. Because command line has the highest
427 # that we just computed. Because command line has the highest
426 # priority, this will always end up in the master_config.
428 # priority, this will always end up in the master_config.
427 self.default_config.Global.cluster_dir = self.cluster_dir
429 self.default_config.Global.cluster_dir = self.cluster_dir
428 self.command_line_config.Global.cluster_dir = self.cluster_dir
430 self.command_line_config.Global.cluster_dir = self.cluster_dir
429
431
430 def find_config_file_name(self):
432 def find_config_file_name(self):
431 """Find the config file name for this application."""
433 """Find the config file name for this application."""
432 # For this type of Application it should be set as a class attribute.
434 # For this type of Application it should be set as a class attribute.
433 if not hasattr(self, 'default_config_file_name'):
435 if not hasattr(self, 'default_config_file_name'):
434 self.log.critical("No config filename found")
436 self.log.critical("No config filename found")
435 else:
437 else:
436 self.config_file_name = self.default_config_file_name
438 self.config_file_name = self.default_config_file_name
437
439
438 def find_config_file_paths(self):
440 def find_config_file_paths(self):
439 # Set the search path to to the cluster directory. We should NOT
441 # Set the search path to to the cluster directory. We should NOT
440 # include IPython.config.default here as the default config files
442 # include IPython.config.default here as the default config files
441 # are ALWAYS automatically moved to the cluster directory.
443 # are ALWAYS automatically moved to the cluster directory.
442 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
444 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
443 self.config_file_paths = (self.cluster_dir,)
445 self.config_file_paths = (self.cluster_dir,)
444
446
445 def pre_construct(self):
447 def pre_construct(self):
446 # The log and security dirs were set earlier, but here we put them
448 # The log and security dirs were set earlier, but here we put them
447 # into the config and log them.
449 # into the config and log them.
448 config = self.master_config
450 config = self.master_config
449 sdir = self.cluster_dir_obj.security_dir
451 sdir = self.cluster_dir_obj.security_dir
450 self.security_dir = config.Global.security_dir = sdir
452 self.security_dir = config.Global.security_dir = sdir
451 ldir = self.cluster_dir_obj.log_dir
453 ldir = self.cluster_dir_obj.log_dir
452 self.log_dir = config.Global.log_dir = ldir
454 self.log_dir = config.Global.log_dir = ldir
453 pdir = self.cluster_dir_obj.pid_dir
455 pdir = self.cluster_dir_obj.pid_dir
454 self.pid_dir = config.Global.pid_dir = pdir
456 self.pid_dir = config.Global.pid_dir = pdir
455 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
457 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
456 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
458 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
457 # Change to the working directory. We do this just before construct
459 # Change to the working directory. We do this just before construct
458 # is called so all the components there have the right working dir.
460 # is called so all the components there have the right working dir.
459 self.to_work_dir()
461 self.to_work_dir()
460
462
461 def to_work_dir(self):
463 def to_work_dir(self):
462 wd = self.master_config.Global.work_dir
464 wd = self.master_config.Global.work_dir
463 if unicode(wd) != unicode(os.getcwd()):
465 if unicode(wd) != unicode(os.getcwd()):
464 os.chdir(wd)
466 os.chdir(wd)
465 self.log.info("Changing to working dir: %s" % wd)
467 self.log.info("Changing to working dir: %s" % wd)
466
468
467 def start_logging(self):
469 def start_logging(self):
468 # Remove old log files
470 # Remove old log files
469 if self.master_config.Global.clean_logs:
471 if self.master_config.Global.clean_logs:
470 log_dir = self.master_config.Global.log_dir
472 log_dir = self.master_config.Global.log_dir
471 for f in os.listdir(log_dir):
473 for f in os.listdir(log_dir):
472 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
474 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
473 # if f.startswith(self.name + u'-') and f.endswith('.log'):
475 # if f.startswith(self.name + u'-') and f.endswith('.log'):
474 os.remove(os.path.join(log_dir, f))
476 os.remove(os.path.join(log_dir, f))
475 # Start logging to the new log file
477 # Start logging to the new log file
476 if self.master_config.Global.log_to_file:
478 if self.master_config.Global.log_to_file:
477 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
479 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
478 logfile = os.path.join(self.log_dir, log_filename)
480 logfile = os.path.join(self.log_dir, log_filename)
479 open_log_file = open(logfile, 'w')
481 open_log_file = open(logfile, 'w')
480 elif self.master_config.Global.log_url:
482 elif self.master_config.Global.log_url:
481 open_log_file = None
483 open_log_file = None
482 else:
484 else:
483 open_log_file = sys.stdout
485 open_log_file = sys.stdout
484 if open_log_file is not None:
486 if open_log_file is not None:
485 self.log.removeHandler(self._log_handler)
487 self.log.removeHandler(self._log_handler)
486 self._log_handler = logging.StreamHandler(open_log_file)
488 self._log_handler = logging.StreamHandler(open_log_file)
487 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
489 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
488 self._log_handler.setFormatter(self._log_formatter)
490 self._log_handler.setFormatter(self._log_formatter)
489 self.log.addHandler(self._log_handler)
491 self.log.addHandler(self._log_handler)
490 # log.startLogging(open_log_file)
492 # log.startLogging(open_log_file)
491
493
492 def write_pid_file(self, overwrite=False):
494 def write_pid_file(self, overwrite=False):
493 """Create a .pid file in the pid_dir with my pid.
495 """Create a .pid file in the pid_dir with my pid.
494
496
495 This must be called after pre_construct, which sets `self.pid_dir`.
497 This must be called after pre_construct, which sets `self.pid_dir`.
496 This raises :exc:`PIDFileError` if the pid file exists already.
498 This raises :exc:`PIDFileError` if the pid file exists already.
497 """
499 """
498 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
500 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
499 if os.path.isfile(pid_file):
501 if os.path.isfile(pid_file):
500 pid = self.get_pid_from_file()
502 pid = self.get_pid_from_file()
501 if not overwrite:
503 if not overwrite:
502 raise PIDFileError(
504 raise PIDFileError(
503 'The pid file [%s] already exists. \nThis could mean that this '
505 'The pid file [%s] already exists. \nThis could mean that this '
504 'server is already running with [pid=%s].' % (pid_file, pid)
506 'server is already running with [pid=%s].' % (pid_file, pid)
505 )
507 )
506 with open(pid_file, 'w') as f:
508 with open(pid_file, 'w') as f:
507 self.log.info("Creating pid file: %s" % pid_file)
509 self.log.info("Creating pid file: %s" % pid_file)
508 f.write(repr(os.getpid())+'\n')
510 f.write(repr(os.getpid())+'\n')
509
511
510 def remove_pid_file(self):
512 def remove_pid_file(self):
511 """Remove the pid file.
513 """Remove the pid file.
512
514
513 This should be called at shutdown by registering a callback with
515 This should be called at shutdown by registering a callback with
514 :func:`reactor.addSystemEventTrigger`. This needs to return
516 :func:`reactor.addSystemEventTrigger`. This needs to return
515 ``None``.
517 ``None``.
516 """
518 """
517 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
519 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
518 if os.path.isfile(pid_file):
520 if os.path.isfile(pid_file):
519 try:
521 try:
520 self.log.info("Removing pid file: %s" % pid_file)
522 self.log.info("Removing pid file: %s" % pid_file)
521 os.remove(pid_file)
523 os.remove(pid_file)
522 except:
524 except:
523 self.log.warn("Error removing the pid file: %s" % pid_file)
525 self.log.warn("Error removing the pid file: %s" % pid_file)
524
526
525 def get_pid_from_file(self):
527 def get_pid_from_file(self):
526 """Get the pid from the pid file.
528 """Get the pid from the pid file.
527
529
528 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
530 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
529 """
531 """
530 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
532 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
531 if os.path.isfile(pid_file):
533 if os.path.isfile(pid_file):
532 with open(pid_file, 'r') as f:
534 with open(pid_file, 'r') as f:
533 pid = int(f.read().strip())
535 pid = int(f.read().strip())
534 return pid
536 return pid
535 else:
537 else:
536 raise PIDFileError('pid file not found: %s' % pid_file)
538 raise PIDFileError('pid file not found: %s' % pid_file)
537
539
540 def check_pid(self, pid):
541 if os.name == 'nt':
542 try:
543 import ctypes
544 # returns 0 if no such process (of ours) exists
545 # positive int otherwise
546 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
547 except Exception:
548 self.log.warn(
549 "Could not determine whether pid %i is running via `OpenProcess`. "
550 " Making the likely assumption that it is."%pid
551 )
552 return True
553 return bool(p)
554 else:
555 try:
556 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
557 output,_ = p.communicate()
558 except OSError:
559 self.log.warn(
560 "Could not determine whether pid %i is running via `ps x`. "
561 " Making the likely assumption that it is."%pid
562 )
563 return True
564 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
565 return pid in pids
566 No newline at end of file
@@ -1,593 +1,617 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 errno
18 import errno
19 import logging
19 import logging
20 import os
20 import os
21 import re
21 import re
22 import signal
22 import signal
23
23
24 from subprocess import check_call, CalledProcessError, PIPE
24 import zmq
25 import zmq
25 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
26
27
27 from IPython.external.argparse import ArgumentParser, SUPPRESS
28 from IPython.external.argparse import ArgumentParser, SUPPRESS
28 from IPython.utils.importstring import import_item
29 from IPython.utils.importstring import import_item
29
30
30 from IPython.parallel.apps.clusterdir import (
31 from IPython.parallel.apps.clusterdir import (
31 ApplicationWithClusterDir, ClusterDirConfigLoader,
32 ApplicationWithClusterDir, ClusterDirConfigLoader,
32 ClusterDirError, PIDFileError
33 ClusterDirError, PIDFileError
33 )
34 )
34
35
35
36
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # Module level variables
38 # Module level variables
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39
40
40
41
41 default_config_file_name = u'ipcluster_config.py'
42 default_config_file_name = u'ipcluster_config.py'
42
43
43
44
44 _description = """\
45 _description = """\
45 Start an IPython cluster for parallel computing.\n\n
46 Start an IPython cluster for parallel computing.\n\n
46
47
47 An IPython cluster consists of 1 controller and 1 or more engines.
48 An IPython cluster consists of 1 controller and 1 or more engines.
48 This command automates the startup of these processes using a wide
49 This command automates the startup of these processes using a wide
49 range of startup methods (SSH, local processes, PBS, mpiexec,
50 range of startup methods (SSH, local processes, PBS, mpiexec,
50 Windows HPC Server 2008). To start a cluster with 4 engines on your
51 Windows HPC Server 2008). To start a cluster with 4 engines on your
51 local host simply do 'ipcluster start -n 4'. For more complex usage
52 local host simply do 'ipcluster start -n 4'. For more complex usage
52 you will typically do 'ipcluster create -p mycluster', then edit
53 you will typically do 'ipcluster create -p mycluster', then edit
53 configuration files, followed by 'ipcluster start -p mycluster -n 4'.
54 configuration files, followed by 'ipcluster start -p mycluster -n 4'.
54 """
55 """
55
56
56
57
57 # Exit codes for ipcluster
58 # Exit codes for ipcluster
58
59
59 # This will be the exit code if the ipcluster appears to be running because
60 # This will be the exit code if the ipcluster appears to be running because
60 # a .pid file exists
61 # a .pid file exists
61 ALREADY_STARTED = 10
62 ALREADY_STARTED = 10
62
63
63
64
64 # This will be the exit code if ipcluster stop is run, but there is not .pid
65 # This will be the exit code if ipcluster stop is run, but there is not .pid
65 # file to be found.
66 # file to be found.
66 ALREADY_STOPPED = 11
67 ALREADY_STOPPED = 11
67
68
68 # This will be the exit code if ipcluster engines is run, but there is not .pid
69 # This will be the exit code if ipcluster engines is run, but there is not .pid
69 # file to be found.
70 # file to be found.
70 NO_CLUSTER = 12
71 NO_CLUSTER = 12
71
72
72
73
73 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
74 # Command line options
75 # Command line options
75 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
76
77
77
78
78 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
79 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
79
80
80 def _add_arguments(self):
81 def _add_arguments(self):
81 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
82 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
82 # its defaults on self.parser. Instead, we will put those on
83 # its defaults on self.parser. Instead, we will put those on
83 # default options on our subparsers.
84 # default options on our subparsers.
84
85
85 # This has all the common options that all subcommands use
86 # This has all the common options that all subcommands use
86 parent_parser1 = ArgumentParser(
87 parent_parser1 = ArgumentParser(
87 add_help=False,
88 add_help=False,
88 argument_default=SUPPRESS
89 argument_default=SUPPRESS
89 )
90 )
90 self._add_ipython_dir(parent_parser1)
91 self._add_ipython_dir(parent_parser1)
91 self._add_log_level(parent_parser1)
92 self._add_log_level(parent_parser1)
92
93
93 # This has all the common options that other subcommands use
94 # This has all the common options that other subcommands use
94 parent_parser2 = ArgumentParser(
95 parent_parser2 = ArgumentParser(
95 add_help=False,
96 add_help=False,
96 argument_default=SUPPRESS
97 argument_default=SUPPRESS
97 )
98 )
98 self._add_cluster_profile(parent_parser2)
99 self._add_cluster_profile(parent_parser2)
99 self._add_cluster_dir(parent_parser2)
100 self._add_cluster_dir(parent_parser2)
100 self._add_work_dir(parent_parser2)
101 self._add_work_dir(parent_parser2)
101 paa = parent_parser2.add_argument
102 paa = parent_parser2.add_argument
102 paa('--log-to-file',
103 paa('--log-to-file',
103 action='store_true', dest='Global.log_to_file',
104 action='store_true', dest='Global.log_to_file',
104 help='Log to a file in the log directory (default is stdout)')
105 help='Log to a file in the log directory (default is stdout)')
105
106
106 # Create the object used to create the subparsers.
107 # Create the object used to create the subparsers.
107 subparsers = self.parser.add_subparsers(
108 subparsers = self.parser.add_subparsers(
108 dest='Global.subcommand',
109 dest='Global.subcommand',
109 title='ipcluster subcommands',
110 title='ipcluster subcommands',
110 description=
111 description=
111 """ipcluster has a variety of subcommands. The general way of
112 """ipcluster has a variety of subcommands. The general way of
112 running ipcluster is 'ipcluster <cmd> [options]'. To get help
113 running ipcluster is 'ipcluster <cmd> [options]'. To get help
113 on a particular subcommand do 'ipcluster <cmd> -h'."""
114 on a particular subcommand do 'ipcluster <cmd> -h'."""
114 # help="For more help, type 'ipcluster <cmd> -h'",
115 # help="For more help, type 'ipcluster <cmd> -h'",
115 )
116 )
116
117
117 # The "list" subcommand parser
118 # The "list" subcommand parser
118 parser_list = subparsers.add_parser(
119 parser_list = subparsers.add_parser(
119 'list',
120 'list',
120 parents=[parent_parser1],
121 parents=[parent_parser1],
121 argument_default=SUPPRESS,
122 argument_default=SUPPRESS,
122 help="List all clusters in cwd and ipython_dir.",
123 help="List all clusters in cwd and ipython_dir.",
123 description=
124 description=
124 """List all available clusters, by cluster directory, that can
125 """List all available clusters, by cluster directory, that can
125 be found in the current working directly or in the ipython
126 be found in the current working directly or in the ipython
126 directory. Cluster directories are named using the convention
127 directory. Cluster directories are named using the convention
127 'cluster_<profile>'."""
128 'cluster_<profile>'."""
128 )
129 )
129
130
130 # The "create" subcommand parser
131 # The "create" subcommand parser
131 parser_create = subparsers.add_parser(
132 parser_create = subparsers.add_parser(
132 'create',
133 'create',
133 parents=[parent_parser1, parent_parser2],
134 parents=[parent_parser1, parent_parser2],
134 argument_default=SUPPRESS,
135 argument_default=SUPPRESS,
135 help="Create a new cluster directory.",
136 help="Create a new cluster directory.",
136 description=
137 description=
137 """Create an ipython cluster directory by its profile name or
138 """Create an ipython cluster directory by its profile name or
138 cluster directory path. Cluster directories contain
139 cluster directory path. Cluster directories contain
139 configuration, log and security related files and are named
140 configuration, log and security related files and are named
140 using the convention 'cluster_<profile>'. By default they are
141 using the convention 'cluster_<profile>'. By default they are
141 located in your ipython directory. Once created, you will
142 located in your ipython directory. Once created, you will
142 probably need to edit the configuration files in the cluster
143 probably need to edit the configuration files in the cluster
143 directory to configure your cluster. Most users will create a
144 directory to configure your cluster. Most users will create a
144 cluster directory by profile name,
145 cluster directory by profile name,
145 'ipcluster create -p mycluster', which will put the directory
146 'ipcluster create -p mycluster', which will put the directory
146 in '<ipython_dir>/cluster_mycluster'.
147 in '<ipython_dir>/cluster_mycluster'.
147 """
148 """
148 )
149 )
149 paa = parser_create.add_argument
150 paa = parser_create.add_argument
150 paa('--reset-config',
151 paa('--reset-config',
151 dest='Global.reset_config', action='store_true',
152 dest='Global.reset_config', action='store_true',
152 help=
153 help=
153 """Recopy the default config files to the cluster directory.
154 """Recopy the default config files to the cluster directory.
154 You will loose any modifications you have made to these files.""")
155 You will loose any modifications you have made to these files.""")
155
156
156 # The "start" subcommand parser
157 # The "start" subcommand parser
157 parser_start = subparsers.add_parser(
158 parser_start = subparsers.add_parser(
158 'start',
159 'start',
159 parents=[parent_parser1, parent_parser2],
160 parents=[parent_parser1, parent_parser2],
160 argument_default=SUPPRESS,
161 argument_default=SUPPRESS,
161 help="Start a cluster.",
162 help="Start a cluster.",
162 description=
163 description=
163 """Start an ipython cluster by its profile name or cluster
164 """Start an ipython cluster by its profile name or cluster
164 directory. Cluster directories contain configuration, log and
165 directory. Cluster directories contain configuration, log and
165 security related files and are named using the convention
166 security related files and are named using the convention
166 'cluster_<profile>' and should be creating using the 'start'
167 'cluster_<profile>' and should be creating using the 'start'
167 subcommand of 'ipcluster'. If your cluster directory is in
168 subcommand of 'ipcluster'. If your cluster directory is in
168 the cwd or the ipython directory, you can simply refer to it
169 the cwd or the ipython directory, you can simply refer to it
169 using its profile name, 'ipcluster start -n 4 -p <profile>`,
170 using its profile name, 'ipcluster start -n 4 -p <profile>`,
170 otherwise use the '--cluster-dir' option.
171 otherwise use the '--cluster-dir' option.
171 """
172 """
172 )
173 )
173
174
174 paa = parser_start.add_argument
175 paa = parser_start.add_argument
175 paa('-n', '--number',
176 paa('-n', '--number',
176 type=int, dest='Global.n',
177 type=int, dest='Global.n',
177 help='The number of engines to start.',
178 help='The number of engines to start.',
178 metavar='Global.n')
179 metavar='Global.n')
179 paa('--clean-logs',
180 paa('--clean-logs',
180 dest='Global.clean_logs', action='store_true',
181 dest='Global.clean_logs', action='store_true',
181 help='Delete old log flies before starting.')
182 help='Delete old log flies before starting.')
182 paa('--no-clean-logs',
183 paa('--no-clean-logs',
183 dest='Global.clean_logs', action='store_false',
184 dest='Global.clean_logs', action='store_false',
184 help="Don't delete old log flies before starting.")
185 help="Don't delete old log flies before starting.")
185 paa('--daemon',
186 paa('--daemon',
186 dest='Global.daemonize', action='store_true',
187 dest='Global.daemonize', action='store_true',
187 help='Daemonize the ipcluster program. This implies --log-to-file')
188 help='Daemonize the ipcluster program. This implies --log-to-file')
188 paa('--no-daemon',
189 paa('--no-daemon',
189 dest='Global.daemonize', action='store_false',
190 dest='Global.daemonize', action='store_false',
190 help="Dont't daemonize the ipcluster program.")
191 help="Dont't daemonize the ipcluster program.")
191 paa('--delay',
192 paa('--delay',
192 type=float, dest='Global.delay',
193 type=float, dest='Global.delay',
193 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
194 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
194
195
195 # The "stop" subcommand parser
196 # The "stop" subcommand parser
196 parser_stop = subparsers.add_parser(
197 parser_stop = subparsers.add_parser(
197 'stop',
198 'stop',
198 parents=[parent_parser1, parent_parser2],
199 parents=[parent_parser1, parent_parser2],
199 argument_default=SUPPRESS,
200 argument_default=SUPPRESS,
200 help="Stop a running cluster.",
201 help="Stop a running cluster.",
201 description=
202 description=
202 """Stop a running ipython cluster by its profile name or cluster
203 """Stop a running ipython cluster by its profile name or cluster
203 directory. Cluster directories are named using the convention
204 directory. Cluster directories are named using the convention
204 'cluster_<profile>'. If your cluster directory is in
205 'cluster_<profile>'. If your cluster directory is in
205 the cwd or the ipython directory, you can simply refer to it
206 the cwd or the ipython directory, you can simply refer to it
206 using its profile name, 'ipcluster stop -p <profile>`, otherwise
207 using its profile name, 'ipcluster stop -p <profile>`, otherwise
207 use the '--cluster-dir' option.
208 use the '--cluster-dir' option.
208 """
209 """
209 )
210 )
210 paa = parser_stop.add_argument
211 paa = parser_stop.add_argument
211 paa('--signal',
212 paa('--signal',
212 dest='Global.signal', type=int,
213 dest='Global.signal', type=int,
213 help="The signal number to use in stopping the cluster (default=2).",
214 help="The signal number to use in stopping the cluster (default=2).",
214 metavar="Global.signal")
215 metavar="Global.signal")
215
216
216 # the "engines" subcommand parser
217 # the "engines" subcommand parser
217 parser_engines = subparsers.add_parser(
218 parser_engines = subparsers.add_parser(
218 'engines',
219 'engines',
219 parents=[parent_parser1, parent_parser2],
220 parents=[parent_parser1, parent_parser2],
220 argument_default=SUPPRESS,
221 argument_default=SUPPRESS,
221 help="Attach some engines to an existing controller or cluster.",
222 help="Attach some engines to an existing controller or cluster.",
222 description=
223 description=
223 """Start one or more engines to connect to an existing Cluster
224 """Start one or more engines to connect to an existing Cluster
224 by profile name or cluster directory.
225 by profile name or cluster directory.
225 Cluster directories contain configuration, log and
226 Cluster directories contain configuration, log and
226 security related files and are named using the convention
227 security related files and are named using the convention
227 'cluster_<profile>' and should be creating using the 'start'
228 'cluster_<profile>' and should be creating using the 'start'
228 subcommand of 'ipcluster'. If your cluster directory is in
229 subcommand of 'ipcluster'. If your cluster directory is in
229 the cwd or the ipython directory, you can simply refer to it
230 the cwd or the ipython directory, you can simply refer to it
230 using its profile name, 'ipcluster engines -n 4 -p <profile>`,
231 using its profile name, 'ipcluster engines -n 4 -p <profile>`,
231 otherwise use the '--cluster-dir' option.
232 otherwise use the '--cluster-dir' option.
232 """
233 """
233 )
234 )
234 paa = parser_engines.add_argument
235 paa = parser_engines.add_argument
235 paa('-n', '--number',
236 paa('-n', '--number',
236 type=int, dest='Global.n',
237 type=int, dest='Global.n',
237 help='The number of engines to start.',
238 help='The number of engines to start.',
238 metavar='Global.n')
239 metavar='Global.n')
239 paa('--daemon',
240 paa('--daemon',
240 dest='Global.daemonize', action='store_true',
241 dest='Global.daemonize', action='store_true',
241 help='Daemonize the ipcluster program. This implies --log-to-file')
242 help='Daemonize the ipcluster program. This implies --log-to-file')
242 paa('--no-daemon',
243 paa('--no-daemon',
243 dest='Global.daemonize', action='store_false',
244 dest='Global.daemonize', action='store_false',
244 help="Dont't daemonize the ipcluster program.")
245 help="Dont't daemonize the ipcluster program.")
245
246
246 #-----------------------------------------------------------------------------
247 #-----------------------------------------------------------------------------
247 # Main application
248 # Main application
248 #-----------------------------------------------------------------------------
249 #-----------------------------------------------------------------------------
249
250
250
251
251 class IPClusterApp(ApplicationWithClusterDir):
252 class IPClusterApp(ApplicationWithClusterDir):
252
253
253 name = u'ipcluster'
254 name = u'ipcluster'
254 description = _description
255 description = _description
255 usage = None
256 usage = None
256 command_line_loader = IPClusterAppConfigLoader
257 command_line_loader = IPClusterAppConfigLoader
257 default_config_file_name = default_config_file_name
258 default_config_file_name = default_config_file_name
258 default_log_level = logging.INFO
259 default_log_level = logging.INFO
259 auto_create_cluster_dir = False
260 auto_create_cluster_dir = False
260
261
261 def create_default_config(self):
262 def create_default_config(self):
262 super(IPClusterApp, self).create_default_config()
263 super(IPClusterApp, self).create_default_config()
263 self.default_config.Global.controller_launcher = \
264 self.default_config.Global.controller_launcher = \
264 'IPython.parallel.apps.launcher.LocalControllerLauncher'
265 'IPython.parallel.apps.launcher.LocalControllerLauncher'
265 self.default_config.Global.engine_launcher = \
266 self.default_config.Global.engine_launcher = \
266 'IPython.parallel.apps.launcher.LocalEngineSetLauncher'
267 'IPython.parallel.apps.launcher.LocalEngineSetLauncher'
267 self.default_config.Global.n = 2
268 self.default_config.Global.n = 2
268 self.default_config.Global.delay = 2
269 self.default_config.Global.delay = 2
269 self.default_config.Global.reset_config = False
270 self.default_config.Global.reset_config = False
270 self.default_config.Global.clean_logs = True
271 self.default_config.Global.clean_logs = True
271 self.default_config.Global.signal = signal.SIGINT
272 self.default_config.Global.signal = signal.SIGINT
272 self.default_config.Global.daemonize = False
273 self.default_config.Global.daemonize = False
273
274
274 def find_resources(self):
275 def find_resources(self):
275 subcommand = self.command_line_config.Global.subcommand
276 subcommand = self.command_line_config.Global.subcommand
276 if subcommand=='list':
277 if subcommand=='list':
277 self.list_cluster_dirs()
278 self.list_cluster_dirs()
278 # Exit immediately because there is nothing left to do.
279 # Exit immediately because there is nothing left to do.
279 self.exit()
280 self.exit()
280 elif subcommand=='create':
281 elif subcommand=='create':
281 self.auto_create_cluster_dir = True
282 self.auto_create_cluster_dir = True
282 super(IPClusterApp, self).find_resources()
283 super(IPClusterApp, self).find_resources()
283 elif subcommand=='start' or subcommand=='stop':
284 elif subcommand=='start' or subcommand=='stop':
284 self.auto_create_cluster_dir = True
285 self.auto_create_cluster_dir = True
285 try:
286 try:
286 super(IPClusterApp, self).find_resources()
287 super(IPClusterApp, self).find_resources()
287 except ClusterDirError:
288 except ClusterDirError:
288 raise ClusterDirError(
289 raise ClusterDirError(
289 "Could not find a cluster directory. A cluster dir must "
290 "Could not find a cluster directory. A cluster dir must "
290 "be created before running 'ipcluster start'. Do "
291 "be created before running 'ipcluster start'. Do "
291 "'ipcluster create -h' or 'ipcluster list -h' for more "
292 "'ipcluster create -h' or 'ipcluster list -h' for more "
292 "information about creating and listing cluster dirs."
293 "information about creating and listing cluster dirs."
293 )
294 )
294 elif subcommand=='engines':
295 elif subcommand=='engines':
295 self.auto_create_cluster_dir = False
296 self.auto_create_cluster_dir = False
296 try:
297 try:
297 super(IPClusterApp, self).find_resources()
298 super(IPClusterApp, self).find_resources()
298 except ClusterDirError:
299 except ClusterDirError:
299 raise ClusterDirError(
300 raise ClusterDirError(
300 "Could not find a cluster directory. A cluster dir must "
301 "Could not find a cluster directory. A cluster dir must "
301 "be created before running 'ipcluster start'. Do "
302 "be created before running 'ipcluster start'. Do "
302 "'ipcluster create -h' or 'ipcluster list -h' for more "
303 "'ipcluster create -h' or 'ipcluster list -h' for more "
303 "information about creating and listing cluster dirs."
304 "information about creating and listing cluster dirs."
304 )
305 )
305
306
306 def list_cluster_dirs(self):
307 def list_cluster_dirs(self):
307 # Find the search paths
308 # Find the search paths
308 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
309 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
309 if cluster_dir_paths:
310 if cluster_dir_paths:
310 cluster_dir_paths = cluster_dir_paths.split(':')
311 cluster_dir_paths = cluster_dir_paths.split(':')
311 else:
312 else:
312 cluster_dir_paths = []
313 cluster_dir_paths = []
313 try:
314 try:
314 ipython_dir = self.command_line_config.Global.ipython_dir
315 ipython_dir = self.command_line_config.Global.ipython_dir
315 except AttributeError:
316 except AttributeError:
316 ipython_dir = self.default_config.Global.ipython_dir
317 ipython_dir = self.default_config.Global.ipython_dir
317 paths = [os.getcwd(), ipython_dir] + \
318 paths = [os.getcwd(), ipython_dir] + \
318 cluster_dir_paths
319 cluster_dir_paths
319 paths = list(set(paths))
320 paths = list(set(paths))
320
321
321 self.log.info('Searching for cluster dirs in paths: %r' % paths)
322 self.log.info('Searching for cluster dirs in paths: %r' % paths)
322 for path in paths:
323 for path in paths:
323 files = os.listdir(path)
324 files = os.listdir(path)
324 for f in files:
325 for f in files:
325 full_path = os.path.join(path, f)
326 full_path = os.path.join(path, f)
326 if os.path.isdir(full_path) and f.startswith('cluster_'):
327 if os.path.isdir(full_path) and f.startswith('cluster_'):
327 profile = full_path.split('_')[-1]
328 profile = full_path.split('_')[-1]
328 start_cmd = 'ipcluster start -p %s -n 4' % profile
329 start_cmd = 'ipcluster start -p %s -n 4' % profile
329 print start_cmd + " ==> " + full_path
330 print start_cmd + " ==> " + full_path
330
331
331 def pre_construct(self):
332 def pre_construct(self):
332 # IPClusterApp.pre_construct() is where we cd to the working directory.
333 # IPClusterApp.pre_construct() is where we cd to the working directory.
333 super(IPClusterApp, self).pre_construct()
334 super(IPClusterApp, self).pre_construct()
334 config = self.master_config
335 config = self.master_config
335 try:
336 try:
336 daemon = config.Global.daemonize
337 daemon = config.Global.daemonize
337 if daemon:
338 if daemon:
338 config.Global.log_to_file = True
339 config.Global.log_to_file = True
339 except AttributeError:
340 except AttributeError:
340 pass
341 pass
341
342
342 def construct(self):
343 def construct(self):
343 config = self.master_config
344 config = self.master_config
344 subcmd = config.Global.subcommand
345 subcmd = config.Global.subcommand
345 reset = config.Global.reset_config
346 reset = config.Global.reset_config
346 if subcmd == 'list':
347 if subcmd == 'list':
347 return
348 return
348 if subcmd == 'create':
349 if subcmd == 'create':
349 self.log.info('Copying default config files to cluster directory '
350 self.log.info('Copying default config files to cluster directory '
350 '[overwrite=%r]' % (reset,))
351 '[overwrite=%r]' % (reset,))
351 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
352 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
352 if subcmd =='start':
353 if subcmd =='start':
353 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
354 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
354 self.start_logging()
355 self.start_logging()
355 self.loop = ioloop.IOLoop.instance()
356 self.loop = ioloop.IOLoop.instance()
356 # reactor.callWhenRunning(self.start_launchers)
357 # reactor.callWhenRunning(self.start_launchers)
357 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
358 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
358 dc.start()
359 dc.start()
359 if subcmd == 'engines':
360 if subcmd == 'engines':
360 self.start_logging()
361 self.start_logging()
361 self.loop = ioloop.IOLoop.instance()
362 self.loop = ioloop.IOLoop.instance()
362 # reactor.callWhenRunning(self.start_launchers)
363 # reactor.callWhenRunning(self.start_launchers)
363 engine_only = lambda : self.start_launchers(controller=False)
364 engine_only = lambda : self.start_launchers(controller=False)
364 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
365 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
365 dc.start()
366 dc.start()
366
367
367 def start_launchers(self, controller=True):
368 def start_launchers(self, controller=True):
368 config = self.master_config
369 config = self.master_config
369
370
370 # Create the launchers. In both bases, we set the work_dir of
371 # Create the launchers. In both bases, we set the work_dir of
371 # the launcher to the cluster_dir. This is where the launcher's
372 # the launcher to the cluster_dir. This is where the launcher's
372 # subprocesses will be launched. It is not where the controller
373 # subprocesses will be launched. It is not where the controller
373 # and engine will be launched.
374 # and engine will be launched.
374 if controller:
375 if controller:
375 cl_class = import_item(config.Global.controller_launcher)
376 cl_class = import_item(config.Global.controller_launcher)
376 self.controller_launcher = cl_class(
377 self.controller_launcher = cl_class(
377 work_dir=self.cluster_dir, config=config,
378 work_dir=self.cluster_dir, config=config,
378 logname=self.log.name
379 logname=self.log.name
379 )
380 )
380 # Setup the observing of stopping. If the controller dies, shut
381 # Setup the observing of stopping. If the controller dies, shut
381 # everything down as that will be completely fatal for the engines.
382 # everything down as that will be completely fatal for the engines.
382 self.controller_launcher.on_stop(self.stop_launchers)
383 self.controller_launcher.on_stop(self.stop_launchers)
383 # But, we don't monitor the stopping of engines. An engine dying
384 # But, we don't monitor the stopping of engines. An engine dying
384 # is just fine and in principle a user could start a new engine.
385 # is just fine and in principle a user could start a new engine.
385 # Also, if we did monitor engine stopping, it is difficult to
386 # Also, if we did monitor engine stopping, it is difficult to
386 # know what to do when only some engines die. Currently, the
387 # know what to do when only some engines die. Currently, the
387 # observing of engine stopping is inconsistent. Some launchers
388 # observing of engine stopping is inconsistent. Some launchers
388 # might trigger on a single engine stopping, other wait until
389 # might trigger on a single engine stopping, other wait until
389 # all stop. TODO: think more about how to handle this.
390 # all stop. TODO: think more about how to handle this.
390 else:
391 else:
391 self.controller_launcher = None
392 self.controller_launcher = None
392
393
393 el_class = import_item(config.Global.engine_launcher)
394 el_class = import_item(config.Global.engine_launcher)
394 self.engine_launcher = el_class(
395 self.engine_launcher = el_class(
395 work_dir=self.cluster_dir, config=config, logname=self.log.name
396 work_dir=self.cluster_dir, config=config, logname=self.log.name
396 )
397 )
397
398
398 # Setup signals
399 # Setup signals
399 signal.signal(signal.SIGINT, self.sigint_handler)
400 signal.signal(signal.SIGINT, self.sigint_handler)
400
401
401 # Start the controller and engines
402 # Start the controller and engines
402 self._stopping = False # Make sure stop_launchers is not called 2x.
403 self._stopping = False # Make sure stop_launchers is not called 2x.
403 if controller:
404 if controller:
404 self.start_controller()
405 self.start_controller()
405 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
406 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
406 dc.start()
407 dc.start()
407 self.startup_message()
408 self.startup_message()
408
409
409 def startup_message(self, r=None):
410 def startup_message(self, r=None):
410 self.log.info("IPython cluster: started")
411 self.log.info("IPython cluster: started")
411 return r
412 return r
412
413
413 def start_controller(self, r=None):
414 def start_controller(self, r=None):
414 # self.log.info("In start_controller")
415 # self.log.info("In start_controller")
415 config = self.master_config
416 config = self.master_config
416 d = self.controller_launcher.start(
417 d = self.controller_launcher.start(
417 cluster_dir=config.Global.cluster_dir
418 cluster_dir=config.Global.cluster_dir
418 )
419 )
419 return d
420 return d
420
421
421 def start_engines(self, r=None):
422 def start_engines(self, r=None):
422 # self.log.info("In start_engines")
423 # self.log.info("In start_engines")
423 config = self.master_config
424 config = self.master_config
424
425
425 d = self.engine_launcher.start(
426 d = self.engine_launcher.start(
426 config.Global.n,
427 config.Global.n,
427 cluster_dir=config.Global.cluster_dir
428 cluster_dir=config.Global.cluster_dir
428 )
429 )
429 return d
430 return d
430
431
431 def stop_controller(self, r=None):
432 def stop_controller(self, r=None):
432 # self.log.info("In stop_controller")
433 # self.log.info("In stop_controller")
433 if self.controller_launcher and self.controller_launcher.running:
434 if self.controller_launcher and self.controller_launcher.running:
434 return self.controller_launcher.stop()
435 return self.controller_launcher.stop()
435
436
436 def stop_engines(self, r=None):
437 def stop_engines(self, r=None):
437 # self.log.info("In stop_engines")
438 # self.log.info("In stop_engines")
438 if self.engine_launcher.running:
439 if self.engine_launcher.running:
439 d = self.engine_launcher.stop()
440 d = self.engine_launcher.stop()
440 # d.addErrback(self.log_err)
441 # d.addErrback(self.log_err)
441 return d
442 return d
442 else:
443 else:
443 return None
444 return None
444
445
445 def log_err(self, f):
446 def log_err(self, f):
446 self.log.error(f.getTraceback())
447 self.log.error(f.getTraceback())
447 return None
448 return None
448
449
449 def stop_launchers(self, r=None):
450 def stop_launchers(self, r=None):
450 if not self._stopping:
451 if not self._stopping:
451 self._stopping = True
452 self._stopping = True
452 # if isinstance(r, failure.Failure):
453 # if isinstance(r, failure.Failure):
453 # self.log.error('Unexpected error in ipcluster:')
454 # self.log.error('Unexpected error in ipcluster:')
454 # self.log.info(r.getTraceback())
455 # self.log.info(r.getTraceback())
455 self.log.error("IPython cluster: stopping")
456 self.log.error("IPython cluster: stopping")
456 # These return deferreds. We are not doing anything with them
457 # These return deferreds. We are not doing anything with them
457 # but we are holding refs to them as a reminder that they
458 # but we are holding refs to them as a reminder that they
458 # do return deferreds.
459 # do return deferreds.
459 d1 = self.stop_engines()
460 d1 = self.stop_engines()
460 d2 = self.stop_controller()
461 d2 = self.stop_controller()
461 # Wait a few seconds to let things shut down.
462 # Wait a few seconds to let things shut down.
462 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
463 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
463 dc.start()
464 dc.start()
464 # reactor.callLater(4.0, reactor.stop)
465 # reactor.callLater(4.0, reactor.stop)
465
466
466 def sigint_handler(self, signum, frame):
467 def sigint_handler(self, signum, frame):
467 self.stop_launchers()
468 self.stop_launchers()
468
469
469 def start_logging(self):
470 def start_logging(self):
470 # Remove old log files of the controller and engine
471 # Remove old log files of the controller and engine
471 if self.master_config.Global.clean_logs:
472 if self.master_config.Global.clean_logs:
472 log_dir = self.master_config.Global.log_dir
473 log_dir = self.master_config.Global.log_dir
473 for f in os.listdir(log_dir):
474 for f in os.listdir(log_dir):
474 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
475 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
475 os.remove(os.path.join(log_dir, f))
476 os.remove(os.path.join(log_dir, f))
476 # This will remove old log files for ipcluster itself
477 # This will remove old log files for ipcluster itself
477 super(IPClusterApp, self).start_logging()
478 super(IPClusterApp, self).start_logging()
478
479
479 def start_app(self):
480 def start_app(self):
480 """Start the application, depending on what subcommand is used."""
481 """Start the application, depending on what subcommand is used."""
481 subcmd = self.master_config.Global.subcommand
482 subcmd = self.master_config.Global.subcommand
482 if subcmd=='create' or subcmd=='list':
483 if subcmd=='create' or subcmd=='list':
483 return
484 return
484 elif subcmd=='start':
485 elif subcmd=='start':
485 self.start_app_start()
486 self.start_app_start()
486 elif subcmd=='stop':
487 elif subcmd=='stop':
487 self.start_app_stop()
488 self.start_app_stop()
488 elif subcmd=='engines':
489 elif subcmd=='engines':
489 self.start_app_engines()
490 self.start_app_engines()
490
491
491 def start_app_start(self):
492 def start_app_start(self):
492 """Start the app for the start subcommand."""
493 """Start the app for the start subcommand."""
493 config = self.master_config
494 config = self.master_config
494 # First see if the cluster is already running
495 # First see if the cluster is already running
495 try:
496 try:
496 pid = self.get_pid_from_file()
497 pid = self.get_pid_from_file()
497 except PIDFileError:
498 except PIDFileError:
498 pass
499 pass
499 else:
500 else:
500 self.log.critical(
501 if self.check_pid(pid):
501 'Cluster is already running with [pid=%s]. '
502 self.log.critical(
502 'use "ipcluster stop" to stop the cluster.' % pid
503 'Cluster is already running with [pid=%s]. '
503 )
504 'use "ipcluster stop" to stop the cluster.' % pid
504 # Here I exit with a unusual exit status that other processes
505 )
505 # can watch for to learn how I existed.
506 # Here I exit with a unusual exit status that other processes
506 self.exit(ALREADY_STARTED)
507 # can watch for to learn how I existed.
508 self.exit(ALREADY_STARTED)
509 else:
510 self.remove_pid_file()
511
507
512
508 # Now log and daemonize
513 # Now log and daemonize
509 self.log.info(
514 self.log.info(
510 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
515 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
511 )
516 )
512 # TODO: Get daemonize working on Windows or as a Windows Server.
517 # TODO: Get daemonize working on Windows or as a Windows Server.
513 if config.Global.daemonize:
518 if config.Global.daemonize:
514 if os.name=='posix':
519 if os.name=='posix':
515 from twisted.scripts._twistd_unix import daemonize
520 from twisted.scripts._twistd_unix import daemonize
516 daemonize()
521 daemonize()
517
522
518 # Now write the new pid file AFTER our new forked pid is active.
523 # Now write the new pid file AFTER our new forked pid is active.
519 self.write_pid_file()
524 self.write_pid_file()
520 try:
525 try:
521 self.loop.start()
526 self.loop.start()
522 except KeyboardInterrupt:
527 except KeyboardInterrupt:
523 pass
528 pass
524 except zmq.ZMQError as e:
529 except zmq.ZMQError as e:
525 if e.errno == errno.EINTR:
530 if e.errno == errno.EINTR:
526 pass
531 pass
527 else:
532 else:
528 raise
533 raise
529 self.remove_pid_file()
534 finally:
535 self.remove_pid_file()
530
536
531 def start_app_engines(self):
537 def start_app_engines(self):
532 """Start the app for the start subcommand."""
538 """Start the app for the start subcommand."""
533 config = self.master_config
539 config = self.master_config
534 # First see if the cluster is already running
540 # First see if the cluster is already running
535
541
536 # Now log and daemonize
542 # Now log and daemonize
537 self.log.info(
543 self.log.info(
538 'Starting engines with [daemon=%r]' % config.Global.daemonize
544 'Starting engines with [daemon=%r]' % config.Global.daemonize
539 )
545 )
540 # TODO: Get daemonize working on Windows or as a Windows Server.
546 # TODO: Get daemonize working on Windows or as a Windows Server.
541 if config.Global.daemonize:
547 if config.Global.daemonize:
542 if os.name=='posix':
548 if os.name=='posix':
543 from twisted.scripts._twistd_unix import daemonize
549 from twisted.scripts._twistd_unix import daemonize
544 daemonize()
550 daemonize()
545
551
546 # Now write the new pid file AFTER our new forked pid is active.
552 # Now write the new pid file AFTER our new forked pid is active.
547 # self.write_pid_file()
553 # self.write_pid_file()
548 try:
554 try:
549 self.loop.start()
555 self.loop.start()
550 except KeyboardInterrupt:
556 except KeyboardInterrupt:
551 pass
557 pass
552 except zmq.ZMQError as e:
558 except zmq.ZMQError as e:
553 if e.errno == errno.EINTR:
559 if e.errno == errno.EINTR:
554 pass
560 pass
555 else:
561 else:
556 raise
562 raise
557 # self.remove_pid_file()
563 # self.remove_pid_file()
558
564
559 def start_app_stop(self):
565 def start_app_stop(self):
560 """Start the app for the stop subcommand."""
566 """Start the app for the stop subcommand."""
561 config = self.master_config
567 config = self.master_config
562 try:
568 try:
563 pid = self.get_pid_from_file()
569 pid = self.get_pid_from_file()
564 except PIDFileError:
570 except PIDFileError:
565 self.log.critical(
571 self.log.critical(
566 'Problem reading pid file, cluster is probably not running.'
572 'Could not read pid file, cluster is probably not running.'
567 )
573 )
568 # Here I exit with a unusual exit status that other processes
574 # Here I exit with a unusual exit status that other processes
569 # can watch for to learn how I existed.
575 # can watch for to learn how I existed.
576 self.remove_pid_file()
570 self.exit(ALREADY_STOPPED)
577 self.exit(ALREADY_STOPPED)
571 else:
578
572 if os.name=='posix':
579 if not self.check_pid(pid):
573 sig = config.Global.signal
580 self.log.critical(
574 self.log.info(
581 'Cluster [pid=%r] is not running.' % pid
575 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
582 )
576 )
583 self.remove_pid_file()
584 # Here I exit with a unusual exit status that other processes
585 # can watch for to learn how I existed.
586 self.exit(ALREADY_STOPPED)
587
588 elif os.name=='posix':
589 sig = config.Global.signal
590 self.log.info(
591 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
592 )
593 try:
577 os.kill(pid, sig)
594 os.kill(pid, sig)
578 elif os.name=='nt':
595 except OSError:
579 # As of right now, we don't support daemonize on Windows, so
596 self.log.error("Stopping cluster failed, assuming already dead.",
580 # stop will not do anything. Minimally, it should clean up the
597 exc_info=True)
581 # old .pid files.
582 self.remove_pid_file()
598 self.remove_pid_file()
599 elif os.name=='nt':
600 try:
601 # kill the whole tree
602 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
603 except (CalledProcessError, OSError):
604 self.log.error("Stopping cluster failed, assuming already dead.",
605 exc_info=True)
606 self.remove_pid_file()
583
607
584
608
585 def launch_new_instance():
609 def launch_new_instance():
586 """Create and run the IPython cluster."""
610 """Create and run the IPython cluster."""
587 app = IPClusterApp()
611 app = IPClusterApp()
588 app.start()
612 app.start()
589
613
590
614
591 if __name__ == '__main__':
615 if __name__ == '__main__':
592 launch_new_instance()
616 launch_new_instance()
593
617
General Comments 0
You need to be logged in to leave comments. Login now