##// END OF EJS Templates
Added .pid files to ipcluster and ipcontroller and daemon mode....
Brian Granger -
Show More
@@ -1,394 +1,458 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
19
18 import os
20 import os
19 import shutil
21 import shutil
20 import sys
22 import sys
21
23
22 from twisted.python import log
24 from twisted.python import log
23
25
24 from IPython.core import release
26 from IPython.core import release
25 from IPython.config.loader import PyFileConfigLoader
27 from IPython.config.loader import PyFileConfigLoader
26 from IPython.core.application import Application
28 from IPython.core.application import Application
27 from IPython.core.component import Component
29 from IPython.core.component import Component
28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
30 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
29 from IPython.utils.traitlets import Unicode, Bool
31 from IPython.utils.traitlets import Unicode, Bool
30
32
31 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
32 # Imports
34 # Imports
33 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
34
36
35
37
36 class ClusterDirError(Exception):
38 class ClusterDirError(Exception):
37 pass
39 pass
38
40
39
41
42 class PIDFileError(Exception):
43 pass
44
45
40 class ClusterDir(Component):
46 class ClusterDir(Component):
41 """An object to manage the cluster directory and its resources.
47 """An object to manage the cluster directory and its resources.
42
48
43 The cluster directory is used by :command:`ipcontroller`,
49 The cluster directory is used by :command:`ipcontroller`,
44 :command:`ipcontroller` and :command:`ipcontroller` to manage the
50 :command:`ipcontroller` and :command:`ipcontroller` to manage the
45 configuration, logging and security of these applications.
51 configuration, logging and security of these applications.
46
52
47 This object knows how to find, create and manage these directories. This
53 This object knows how to find, create and manage these directories. This
48 should be used by any code that want's to handle cluster directories.
54 should be used by any code that want's to handle cluster directories.
49 """
55 """
50
56
51 security_dir_name = Unicode('security')
57 security_dir_name = Unicode('security')
52 log_dir_name = Unicode('log')
58 log_dir_name = Unicode('log')
53 security_dir = Unicode()
59 pid_dir_name = Unicode('pid')
54 log_dir = Unicode('')
60 security_dir = Unicode(u'')
55 location = Unicode('')
61 log_dir = Unicode(u'')
62 pid_dir = Unicode(u'')
63 location = Unicode(u'')
56
64
57 def __init__(self, location):
65 def __init__(self, location):
58 super(ClusterDir, self).__init__(None)
66 super(ClusterDir, self).__init__(None)
59 self.location = location
67 self.location = location
60
68
61 def _location_changed(self, name, old, new):
69 def _location_changed(self, name, old, new):
62 if not os.path.isdir(new):
70 if not os.path.isdir(new):
63 os.makedirs(new, mode=0777)
71 os.makedirs(new, mode=0777)
64 else:
72 else:
65 os.chmod(new, 0777)
73 os.chmod(new, 0777)
66 self.security_dir = os.path.join(new, self.security_dir_name)
74 self.security_dir = os.path.join(new, self.security_dir_name)
67 self.log_dir = os.path.join(new, self.log_dir_name)
75 self.log_dir = os.path.join(new, self.log_dir_name)
76 self.pid_dir = os.path.join(new, self.pid_dir_name)
68 self.check_dirs()
77 self.check_dirs()
69
78
70 def _log_dir_changed(self, name, old, new):
79 def _log_dir_changed(self, name, old, new):
71 self.check_log_dir()
80 self.check_log_dir()
72
81
73 def check_log_dir(self):
82 def check_log_dir(self):
74 if not os.path.isdir(self.log_dir):
83 if not os.path.isdir(self.log_dir):
75 os.mkdir(self.log_dir, 0777)
84 os.mkdir(self.log_dir, 0777)
76 else:
85 else:
77 os.chmod(self.log_dir, 0777)
86 os.chmod(self.log_dir, 0777)
78
87
79 def _security_dir_changed(self, name, old, new):
88 def _security_dir_changed(self, name, old, new):
80 self.check_security_dir()
89 self.check_security_dir()
81
90
82 def check_security_dir(self):
91 def check_security_dir(self):
83 if not os.path.isdir(self.security_dir):
92 if not os.path.isdir(self.security_dir):
84 os.mkdir(self.security_dir, 0700)
93 os.mkdir(self.security_dir, 0700)
85 else:
94 else:
86 os.chmod(self.security_dir, 0700)
95 os.chmod(self.security_dir, 0700)
87
96
97 def _pid_dir_changed(self, name, old, new):
98 self.check_pid_dir()
99
100 def check_pid_dir(self):
101 if not os.path.isdir(self.pid_dir):
102 os.mkdir(self.pid_dir, 0700)
103 else:
104 os.chmod(self.pid_dir, 0700)
105
88 def check_dirs(self):
106 def check_dirs(self):
89 self.check_security_dir()
107 self.check_security_dir()
90 self.check_log_dir()
108 self.check_log_dir()
109 self.check_pid_dir()
91
110
92 def load_config_file(self, filename):
111 def load_config_file(self, filename):
93 """Load a config file from the top level of the cluster dir.
112 """Load a config file from the top level of the cluster dir.
94
113
95 Parameters
114 Parameters
96 ----------
115 ----------
97 filename : unicode or str
116 filename : unicode or str
98 The filename only of the config file that must be located in
117 The filename only of the config file that must be located in
99 the top-level of the cluster directory.
118 the top-level of the cluster directory.
100 """
119 """
101 loader = PyFileConfigLoader(filename, self.location)
120 loader = PyFileConfigLoader(filename, self.location)
102 return loader.load_config()
121 return loader.load_config()
103
122
104 def copy_config_file(self, config_file, path=None, overwrite=False):
123 def copy_config_file(self, config_file, path=None, overwrite=False):
105 """Copy a default config file into the active cluster directory.
124 """Copy a default config file into the active cluster directory.
106
125
107 Default configuration files are kept in :mod:`IPython.config.default`.
126 Default configuration files are kept in :mod:`IPython.config.default`.
108 This function moves these from that location to the working cluster
127 This function moves these from that location to the working cluster
109 directory.
128 directory.
110 """
129 """
111 if path is None:
130 if path is None:
112 import IPython.config.default
131 import IPython.config.default
113 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
132 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
114 path = os.path.sep.join(path)
133 path = os.path.sep.join(path)
115 src = os.path.join(path, config_file)
134 src = os.path.join(path, config_file)
116 dst = os.path.join(self.location, config_file)
135 dst = os.path.join(self.location, config_file)
117 if not os.path.isfile(dst) or overwrite:
136 if not os.path.isfile(dst) or overwrite:
118 shutil.copy(src, dst)
137 shutil.copy(src, dst)
119
138
120 def copy_all_config_files(self, path=None, overwrite=False):
139 def copy_all_config_files(self, path=None, overwrite=False):
121 """Copy all config files into the active cluster directory."""
140 """Copy all config files into the active cluster directory."""
122 for f in ['ipcontroller_config.py', 'ipengine_config.py',
141 for f in ['ipcontroller_config.py', 'ipengine_config.py',
123 'ipcluster_config.py']:
142 'ipcluster_config.py']:
124 self.copy_config_file(f, path=path, overwrite=overwrite)
143 self.copy_config_file(f, path=path, overwrite=overwrite)
125
144
126 @classmethod
145 @classmethod
127 def create_cluster_dir(csl, cluster_dir):
146 def create_cluster_dir(csl, cluster_dir):
128 """Create a new cluster directory given a full path.
147 """Create a new cluster directory given a full path.
129
148
130 Parameters
149 Parameters
131 ----------
150 ----------
132 cluster_dir : str
151 cluster_dir : str
133 The full path to the cluster directory. If it does exist, it will
152 The full path to the cluster directory. If it does exist, it will
134 be used. If not, it will be created.
153 be used. If not, it will be created.
135 """
154 """
136 return ClusterDir(cluster_dir)
155 return ClusterDir(cluster_dir)
137
156
138 @classmethod
157 @classmethod
139 def create_cluster_dir_by_profile(cls, path, profile='default'):
158 def create_cluster_dir_by_profile(cls, path, profile='default'):
140 """Create a cluster dir by profile name and path.
159 """Create a cluster dir by profile name and path.
141
160
142 Parameters
161 Parameters
143 ----------
162 ----------
144 path : str
163 path : str
145 The path (directory) to put the cluster directory in.
164 The path (directory) to put the cluster directory in.
146 profile : str
165 profile : str
147 The name of the profile. The name of the cluster directory will
166 The name of the profile. The name of the cluster directory will
148 be "cluster_<profile>".
167 be "cluster_<profile>".
149 """
168 """
150 if not os.path.isdir(path):
169 if not os.path.isdir(path):
151 raise ClusterDirError('Directory not found: %s' % path)
170 raise ClusterDirError('Directory not found: %s' % path)
152 cluster_dir = os.path.join(path, 'cluster_' + profile)
171 cluster_dir = os.path.join(path, 'cluster_' + profile)
153 return ClusterDir(cluster_dir)
172 return ClusterDir(cluster_dir)
154
173
155 @classmethod
174 @classmethod
156 def find_cluster_dir_by_profile(cls, ipythondir, profile='default'):
175 def find_cluster_dir_by_profile(cls, ipythondir, profile='default'):
157 """Find an existing cluster dir by profile name, return its ClusterDir.
176 """Find an existing cluster dir by profile name, return its ClusterDir.
158
177
159 This searches through a sequence of paths for a cluster dir. If it
178 This searches through a sequence of paths for a cluster dir. If it
160 is not found, a :class:`ClusterDirError` exception will be raised.
179 is not found, a :class:`ClusterDirError` exception will be raised.
161
180
162 The search path algorithm is:
181 The search path algorithm is:
163 1. ``os.getcwd()``
182 1. ``os.getcwd()``
164 2. ``ipythondir``
183 2. ``ipythondir``
165 3. The directories found in the ":" separated
184 3. The directories found in the ":" separated
166 :env:`IPCLUSTERDIR_PATH` environment variable.
185 :env:`IPCLUSTERDIR_PATH` environment variable.
167
186
168 Parameters
187 Parameters
169 ----------
188 ----------
170 ipythondir : unicode or str
189 ipythondir : unicode or str
171 The IPython directory to use.
190 The IPython directory to use.
172 profile : unicode or str
191 profile : unicode or str
173 The name of the profile. The name of the cluster directory
192 The name of the profile. The name of the cluster directory
174 will be "cluster_<profile>".
193 will be "cluster_<profile>".
175 """
194 """
176 dirname = 'cluster_' + profile
195 dirname = 'cluster_' + profile
177 cluster_dir_paths = os.environ.get('IPCLUSTERDIR_PATH','')
196 cluster_dir_paths = os.environ.get('IPCLUSTERDIR_PATH','')
178 if cluster_dir_paths:
197 if cluster_dir_paths:
179 cluster_dir_paths = cluster_dir_paths.split(':')
198 cluster_dir_paths = cluster_dir_paths.split(':')
180 else:
199 else:
181 cluster_dir_paths = []
200 cluster_dir_paths = []
182 paths = [os.getcwd(), ipythondir] + cluster_dir_paths
201 paths = [os.getcwd(), ipythondir] + cluster_dir_paths
183 for p in paths:
202 for p in paths:
184 cluster_dir = os.path.join(p, dirname)
203 cluster_dir = os.path.join(p, dirname)
185 if os.path.isdir(cluster_dir):
204 if os.path.isdir(cluster_dir):
186 return ClusterDir(cluster_dir)
205 return ClusterDir(cluster_dir)
187 else:
206 else:
188 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
207 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
189
208
190 @classmethod
209 @classmethod
191 def find_cluster_dir(cls, cluster_dir):
210 def find_cluster_dir(cls, cluster_dir):
192 """Find/create a cluster dir and return its ClusterDir.
211 """Find/create a cluster dir and return its ClusterDir.
193
212
194 This will create the cluster directory if it doesn't exist.
213 This will create the cluster directory if it doesn't exist.
195
214
196 Parameters
215 Parameters
197 ----------
216 ----------
198 cluster_dir : unicode or str
217 cluster_dir : unicode or str
199 The path of the cluster directory. This is expanded using
218 The path of the cluster directory. This is expanded using
200 :func:`os.path.expandvars` and :func:`os.path.expanduser`.
219 :func:`os.path.expandvars` and :func:`os.path.expanduser`.
201 """
220 """
202 cluster_dir = os.path.expandvars(os.path.expanduser(cluster_dir))
221 cluster_dir = os.path.expandvars(os.path.expanduser(cluster_dir))
203 if not os.path.isdir(cluster_dir):
222 if not os.path.isdir(cluster_dir):
204 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
223 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
205 return ClusterDir(cluster_dir)
224 return ClusterDir(cluster_dir)
206
225
207
226
208 class AppWithClusterDirArgParseConfigLoader(ArgParseConfigLoader):
227 class AppWithClusterDirArgParseConfigLoader(ArgParseConfigLoader):
209 """Default command line options for IPython cluster applications."""
228 """Default command line options for IPython cluster applications."""
210
229
211 def _add_other_arguments(self):
230 def _add_other_arguments(self):
212 self.parser.add_argument('-ipythondir', '--ipython-dir',
231 self.parser.add_argument('-ipythondir', '--ipython-dir',
213 dest='Global.ipythondir',type=str,
232 dest='Global.ipythondir',type=str,
214 help='Set to override default location of Global.ipythondir.',
233 help='Set to override default location of Global.ipythondir.',
215 default=NoConfigDefault,
234 default=NoConfigDefault,
216 metavar='Global.ipythondir'
235 metavar='Global.ipythondir'
217 )
236 )
218 self.parser.add_argument('-p','-profile', '--profile',
237 self.parser.add_argument('-p','-profile', '--profile',
219 dest='Global.profile',type=str,
238 dest='Global.profile',type=str,
220 help='The string name of the profile to be used. This determines '
239 help='The string name of the profile to be used. This determines '
221 'the name of the cluster dir as: cluster_<profile>. The default profile '
240 'the name of the cluster dir as: cluster_<profile>. The default profile '
222 'is named "default". The cluster directory is resolve this way '
241 'is named "default". The cluster directory is resolve this way '
223 'if the --cluster-dir option is not used.',
242 'if the --cluster-dir option is not used.',
224 default=NoConfigDefault,
243 default=NoConfigDefault,
225 metavar='Global.profile'
244 metavar='Global.profile'
226 )
245 )
227 self.parser.add_argument('-log_level', '--log-level',
246 self.parser.add_argument('-log_level', '--log-level',
228 dest="Global.log_level",type=int,
247 dest="Global.log_level",type=int,
229 help='Set the log level (0,10,20,30,40,50). Default is 30.',
248 help='Set the log level (0,10,20,30,40,50). Default is 30.',
230 default=NoConfigDefault,
249 default=NoConfigDefault,
231 metavar="Global.log_level"
250 metavar="Global.log_level"
232 )
251 )
233 self.parser.add_argument('-cluster_dir', '--cluster-dir',
252 self.parser.add_argument('-cluster_dir', '--cluster-dir',
234 dest='Global.cluster_dir',type=str,
253 dest='Global.cluster_dir',type=str,
235 help='Set the cluster dir. This overrides the logic used by the '
254 help='Set the cluster dir. This overrides the logic used by the '
236 '--profile option.',
255 '--profile option.',
237 default=NoConfigDefault,
256 default=NoConfigDefault,
238 metavar='Global.cluster_dir'
257 metavar='Global.cluster_dir'
239 )
258 )
240 self.parser.add_argument('-clean_logs', '--clean-logs',
259 self.parser.add_argument('-clean_logs', '--clean-logs',
241 dest='Global.clean_logs', action='store_true',
260 dest='Global.clean_logs', action='store_true',
242 help='Delete old log flies before starting.',
261 help='Delete old log flies before starting.',
243 default=NoConfigDefault
262 default=NoConfigDefault
244 )
263 )
245 self.parser.add_argument('-noclean_logs', '--no-clean-logs',
264 self.parser.add_argument('-noclean_logs', '--no-clean-logs',
246 dest='Global.clean_logs', action='store_false',
265 dest='Global.clean_logs', action='store_false',
247 help="Don't Delete old log flies before starting.",
266 help="Don't Delete old log flies before starting.",
248 default=NoConfigDefault
267 default=NoConfigDefault
249 )
268 )
250
269
251 class ApplicationWithClusterDir(Application):
270 class ApplicationWithClusterDir(Application):
252 """An application that puts everything into a cluster directory.
271 """An application that puts everything into a cluster directory.
253
272
254 Instead of looking for things in the ipythondir, this type of application
273 Instead of looking for things in the ipythondir, this type of application
255 will use its own private directory called the "cluster directory"
274 will use its own private directory called the "cluster directory"
256 for things like config files, log files, etc.
275 for things like config files, log files, etc.
257
276
258 The cluster directory is resolved as follows:
277 The cluster directory is resolved as follows:
259
278
260 * If the ``--cluster-dir`` option is given, it is used.
279 * If the ``--cluster-dir`` option is given, it is used.
261 * If ``--cluster-dir`` is not given, the application directory is
280 * If ``--cluster-dir`` is not given, the application directory is
262 resolve using the profile name as ``cluster_<profile>``. The search
281 resolve using the profile name as ``cluster_<profile>``. The search
263 path for this directory is then i) cwd if it is found there
282 path for this directory is then i) cwd if it is found there
264 and ii) in ipythondir otherwise.
283 and ii) in ipythondir otherwise.
265
284
266 The config file for the application is to be put in the cluster
285 The config file for the application is to be put in the cluster
267 dir and named the value of the ``config_file_name`` class attribute.
286 dir and named the value of the ``config_file_name`` class attribute.
268 """
287 """
269
288
270 auto_create_cluster_dir = True
289 auto_create_cluster_dir = True
271
290
272 def create_default_config(self):
291 def create_default_config(self):
273 super(ApplicationWithClusterDir, self).create_default_config()
292 super(ApplicationWithClusterDir, self).create_default_config()
274 self.default_config.Global.profile = 'default'
293 self.default_config.Global.profile = 'default'
275 self.default_config.Global.cluster_dir = ''
294 self.default_config.Global.cluster_dir = ''
276 self.default_config.Global.log_to_file = False
295 self.default_config.Global.log_to_file = False
277 self.default_config.Global.clean_logs = False
296 self.default_config.Global.clean_logs = False
278
297
279 def create_command_line_config(self):
298 def create_command_line_config(self):
280 """Create and return a command line config loader."""
299 """Create and return a command line config loader."""
281 return AppWithClusterDirArgParseConfigLoader(
300 return AppWithClusterDirArgParseConfigLoader(
282 description=self.description,
301 description=self.description,
283 version=release.version
302 version=release.version
284 )
303 )
285
304
286 def find_resources(self):
305 def find_resources(self):
287 """This resolves the cluster directory.
306 """This resolves the cluster directory.
288
307
289 This tries to find the cluster directory and if successful, it will
308 This tries to find the cluster directory and if successful, it will
290 have done:
309 have done:
291 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
310 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
292 the application.
311 the application.
293 * Sets ``self.cluster_dir`` attribute of the application and config
312 * Sets ``self.cluster_dir`` attribute of the application and config
294 objects.
313 objects.
295
314
296 The algorithm used for this is as follows:
315 The algorithm used for this is as follows:
297 1. Try ``Global.cluster_dir``.
316 1. Try ``Global.cluster_dir``.
298 2. Try using ``Global.profile``.
317 2. Try using ``Global.profile``.
299 3. If both of these fail and ``self.auto_create_cluster_dir`` is
318 3. If both of these fail and ``self.auto_create_cluster_dir`` is
300 ``True``, then create the new cluster dir in the IPython directory.
319 ``True``, then create the new cluster dir in the IPython directory.
301 4. If all fails, then raise :class:`ClusterDirError`.
320 4. If all fails, then raise :class:`ClusterDirError`.
302 """
321 """
303
322
304 try:
323 try:
305 cluster_dir = self.command_line_config.Global.cluster_dir
324 cluster_dir = self.command_line_config.Global.cluster_dir
306 except AttributeError:
325 except AttributeError:
307 cluster_dir = self.default_config.Global.cluster_dir
326 cluster_dir = self.default_config.Global.cluster_dir
308 cluster_dir = os.path.expandvars(os.path.expanduser(cluster_dir))
327 cluster_dir = os.path.expandvars(os.path.expanduser(cluster_dir))
309 try:
328 try:
310 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
329 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
311 except ClusterDirError:
330 except ClusterDirError:
312 pass
331 pass
313 else:
332 else:
314 self.log.info('Using existing cluster dir: %s' % \
333 self.log.info('Using existing cluster dir: %s' % \
315 self.cluster_dir_obj.location
334 self.cluster_dir_obj.location
316 )
335 )
317 self.finish_cluster_dir()
336 self.finish_cluster_dir()
318 return
337 return
319
338
320 try:
339 try:
321 self.profile = self.command_line_config.Global.profile
340 self.profile = self.command_line_config.Global.profile
322 except AttributeError:
341 except AttributeError:
323 self.profile = self.default_config.Global.profile
342 self.profile = self.default_config.Global.profile
324 try:
343 try:
325 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
344 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
326 self.ipythondir, self.profile)
345 self.ipythondir, self.profile)
327 except ClusterDirError:
346 except ClusterDirError:
328 pass
347 pass
329 else:
348 else:
330 self.log.info('Using existing cluster dir: %s' % \
349 self.log.info('Using existing cluster dir: %s' % \
331 self.cluster_dir_obj.location
350 self.cluster_dir_obj.location
332 )
351 )
333 self.finish_cluster_dir()
352 self.finish_cluster_dir()
334 return
353 return
335
354
336 if self.auto_create_cluster_dir:
355 if self.auto_create_cluster_dir:
337 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
356 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
338 self.ipythondir, self.profile
357 self.ipythondir, self.profile
339 )
358 )
340 self.log.info('Creating new cluster dir: %s' % \
359 self.log.info('Creating new cluster dir: %s' % \
341 self.cluster_dir_obj.location
360 self.cluster_dir_obj.location
342 )
361 )
343 self.finish_cluster_dir()
362 self.finish_cluster_dir()
344 else:
363 else:
345 raise ClusterDirError('Could not find a valid cluster directory.')
364 raise ClusterDirError('Could not find a valid cluster directory.')
346
365
347 def finish_cluster_dir(self):
366 def finish_cluster_dir(self):
348 # Set the cluster directory
367 # Set the cluster directory
349 self.cluster_dir = self.cluster_dir_obj.location
368 self.cluster_dir = self.cluster_dir_obj.location
350
369
351 # These have to be set because they could be different from the one
370 # These have to be set because they could be different from the one
352 # that we just computed. Because command line has the highest
371 # that we just computed. Because command line has the highest
353 # priority, this will always end up in the master_config.
372 # priority, this will always end up in the master_config.
354 self.default_config.Global.cluster_dir = self.cluster_dir
373 self.default_config.Global.cluster_dir = self.cluster_dir
355 self.command_line_config.Global.cluster_dir = self.cluster_dir
374 self.command_line_config.Global.cluster_dir = self.cluster_dir
356
375
357 # Set the search path to the cluster directory
376 # Set the search path to the cluster directory
358 self.config_file_paths = (self.cluster_dir,)
377 self.config_file_paths = (self.cluster_dir,)
359
378
360 def find_config_file_name(self):
379 def find_config_file_name(self):
361 """Find the config file name for this application."""
380 """Find the config file name for this application."""
362 # For this type of Application it should be set as a class attribute.
381 # For this type of Application it should be set as a class attribute.
363 if not hasattr(self, 'config_file_name'):
382 if not hasattr(self, 'config_file_name'):
364 self.log.critical("No config filename found")
383 self.log.critical("No config filename found")
365
384
366 def find_config_file_paths(self):
385 def find_config_file_paths(self):
367 # Set the search path to the cluster directory
386 # Set the search path to the cluster directory
368 self.config_file_paths = (self.cluster_dir,)
387 self.config_file_paths = (self.cluster_dir,)
369
388
370 def pre_construct(self):
389 def pre_construct(self):
371 # The log and security dirs were set earlier, but here we put them
390 # The log and security dirs were set earlier, but here we put them
372 # into the config and log them.
391 # into the config and log them.
373 config = self.master_config
392 config = self.master_config
374 sdir = self.cluster_dir_obj.security_dir
393 sdir = self.cluster_dir_obj.security_dir
375 self.security_dir = config.Global.security_dir = sdir
394 self.security_dir = config.Global.security_dir = sdir
376 ldir = self.cluster_dir_obj.log_dir
395 ldir = self.cluster_dir_obj.log_dir
377 self.log_dir = config.Global.log_dir = ldir
396 self.log_dir = config.Global.log_dir = ldir
397 pdir = self.cluster_dir_obj.pid_dir
398 self.pid_dir = config.Global.pid_dir = pdir
378 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
399 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
379
400
380 def start_logging(self):
401 def start_logging(self):
381 # Remove old log files
402 # Remove old log files
382 if self.master_config.Global.clean_logs:
403 if self.master_config.Global.clean_logs:
383 log_dir = self.master_config.Global.log_dir
404 log_dir = self.master_config.Global.log_dir
384 for f in os.listdir(log_dir):
405 for f in os.listdir(log_dir):
385 if f.startswith(self.name + '-') and f.endswith('.log'):
406 if f.startswith(self.name + '-') and f.endswith('.log'):
386 os.remove(os.path.join(log_dir, f))
407 os.remove(os.path.join(log_dir, f))
387 # Start logging to the new log file
408 # Start logging to the new log file
388 if self.master_config.Global.log_to_file:
409 if self.master_config.Global.log_to_file:
389 log_filename = self.name + '-' + str(os.getpid()) + '.log'
410 log_filename = self.name + '-' + str(os.getpid()) + '.log'
390 logfile = os.path.join(self.log_dir, log_filename)
411 logfile = os.path.join(self.log_dir, log_filename)
391 open_log_file = open(logfile, 'w')
412 open_log_file = open(logfile, 'w')
392 else:
413 else:
393 open_log_file = sys.stdout
414 open_log_file = sys.stdout
394 log.startLogging(open_log_file)
415 log.startLogging(open_log_file)
416
417 def write_pid_file(self):
418 """Create a .pid file in the pid_dir with my pid.
419
420 This must be called after pre_construct, which sets `self.pid_dir`.
421 This raises :exc:`PIDFileError` if the pid file exists already.
422 """
423 pid_file = os.path.join(self.pid_dir, self.name + '.pid')
424 if os.path.isfile(pid_file):
425 pid = self.get_pid_from_file()
426 raise PIDFileError(
427 'The pid file [%s] already exists. \nThis could mean that this '
428 'server is already running with [pid=%s].' % (pid_file, pid))
429 with open(pid_file, 'w') as f:
430 self.log.info("Creating pid file: %s" % pid_file)
431 f.write(repr(os.getpid())+'\n')
432
433 def remove_pid_file(self):
434 """Remove the pid file.
435
436 This should be called at shutdown by registering a callback with
437 :func:`reactor.addSystemEventTrigger`.
438 """
439 pid_file = os.path.join(self.pid_dir, self.name + '.pid')
440 if os.path.isfile(pid_file):
441 try:
442 self.log.info("Removing pid file: %s" % pid_file)
443 os.remove(pid_file)
444 except:
445 pass
446
447 def get_pid_from_file(self):
448 """Get the pid from the pid file.
449
450 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
451 """
452 pid_file = os.path.join(self.pid_dir, self.name + '.pid')
453 if os.path.isfile(pid_file):
454 with open(pid_file, 'r') as f:
455 pid = int(f.read().strip())
456 return pid
457 else:
458 raise PIDFileError('pid file not found: %s' % pid_file) No newline at end of file
@@ -1,306 +1,378 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The ipcluster application.
4 The ipcluster application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import logging
18 import logging
19 import os
19 import os
20 import signal
20 import signal
21 import sys
21 import sys
22
22
23 from twisted.scripts._twistd_unix import daemonize
24
23 from IPython.core import release
25 from IPython.core import release
24 from IPython.external import argparse
26 from IPython.external import argparse
25 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
27 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
26 from IPython.utils.importstring import import_item
28 from IPython.utils.importstring import import_item
27
29
28 from IPython.kernel.clusterdir import (
30 from IPython.kernel.clusterdir import (
29 ApplicationWithClusterDir, ClusterDirError
31 ApplicationWithClusterDir, ClusterDirError, PIDFileError
30 )
32 )
31
33
32 from twisted.internet import reactor, defer
34 from twisted.internet import reactor, defer
33 from twisted.python import log
35 from twisted.python import log
34
36
35 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
36 # Code for launchers
38 # Code for launchers
37 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
38
40
39
41
40
42
41 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
42 # The ipcluster application
44 # The ipcluster application
43 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
44
46
45
47
46 class IPClusterCLLoader(ArgParseConfigLoader):
48 class IPClusterCLLoader(ArgParseConfigLoader):
47
49
48 def _add_arguments(self):
50 def _add_arguments(self):
49 # This has all the common options that all subcommands use
51 # This has all the common options that all subcommands use
50 parent_parser1 = argparse.ArgumentParser(add_help=False)
52 parent_parser1 = argparse.ArgumentParser(add_help=False)
51 parent_parser1.add_argument('-ipythondir', '--ipython-dir',
53 parent_parser1.add_argument('-ipythondir', '--ipython-dir',
52 dest='Global.ipythondir',type=str,
54 dest='Global.ipythondir',type=str,
53 help='Set to override default location of Global.ipythondir.',
55 help='Set to override default location of Global.ipythondir.',
54 default=NoConfigDefault,
56 default=NoConfigDefault,
55 metavar='Global.ipythondir')
57 metavar='Global.ipythondir')
56 parent_parser1.add_argument('-log_level', '--log-level',
58 parent_parser1.add_argument('-log_level', '--log-level',
57 dest="Global.log_level",type=int,
59 dest="Global.log_level",type=int,
58 help='Set the log level (0,10,20,30,40,50). Default is 30.',
60 help='Set the log level (0,10,20,30,40,50). Default is 30.',
59 default=NoConfigDefault,
61 default=NoConfigDefault,
60 metavar='Global.log_level')
62 metavar='Global.log_level')
61
63
62 # This has all the common options that other subcommands use
64 # This has all the common options that other subcommands use
63 parent_parser2 = argparse.ArgumentParser(add_help=False)
65 parent_parser2 = argparse.ArgumentParser(add_help=False)
64 parent_parser2.add_argument('-p','-profile', '--profile',
66 parent_parser2.add_argument('-p','-profile', '--profile',
65 dest='Global.profile',type=str,
67 dest='Global.profile',type=str,
66 default=NoConfigDefault,
68 default=NoConfigDefault,
67 help='The string name of the profile to be used. This determines '
69 help='The string name of the profile to be used. This determines '
68 'the name of the cluster dir as: cluster_<profile>. The default profile '
70 'the name of the cluster dir as: cluster_<profile>. The default profile '
69 'is named "default". The cluster directory is resolve this way '
71 'is named "default". The cluster directory is resolve this way '
70 'if the --cluster-dir option is not used.',
72 'if the --cluster-dir option is not used.',
71 default=NoConfigDefault,
73 default=NoConfigDefault,
72 metavar='Global.profile')
74 metavar='Global.profile')
73 parent_parser2.add_argument('-cluster_dir', '--cluster-dir',
75 parent_parser2.add_argument('-cluster_dir', '--cluster-dir',
74 dest='Global.cluster_dir',type=str,
76 dest='Global.cluster_dir',type=str,
75 default=NoConfigDefault,
77 default=NoConfigDefault,
76 help='Set the cluster dir. This overrides the logic used by the '
78 help='Set the cluster dir. This overrides the logic used by the '
77 '--profile option.',
79 '--profile option.',
78 default=NoConfigDefault,
80 default=NoConfigDefault,
79 metavar='Global.cluster_dir')
81 metavar='Global.cluster_dir')
80 parent_parser2.add_argument('--log-to-file',
82 parent_parser2.add_argument('--log-to-file',
81 action='store_true', dest='Global.log_to_file',
83 action='store_true', dest='Global.log_to_file',
82 default=NoConfigDefault,
84 default=NoConfigDefault,
83 help='Log to a file in the log directory (default is stdout)'
85 help='Log to a file in the log directory (default is stdout)'
84 )
86 )
85
87
86 subparsers = self.parser.add_subparsers(
88 subparsers = self.parser.add_subparsers(
87 dest='Global.subcommand',
89 dest='Global.subcommand',
88 title='ipcluster subcommands',
90 title='ipcluster subcommands',
89 description='ipcluster has a variety of subcommands. '
91 description='ipcluster has a variety of subcommands. '
90 'The general way of running ipcluster is "ipcluster <cmd> '
92 'The general way of running ipcluster is "ipcluster <cmd> '
91 ' [options]""',
93 ' [options]""',
92 help='For more help, type "ipcluster <cmd> -h"')
94 help='For more help, type "ipcluster <cmd> -h"')
93
95
94 parser_list = subparsers.add_parser(
96 parser_list = subparsers.add_parser(
95 'list',
97 'list',
96 help='List all clusters in cwd and ipythondir.',
98 help='List all clusters in cwd and ipythondir.',
97 parents=[parent_parser1]
99 parents=[parent_parser1]
98 )
100 )
99
101
100 parser_create = subparsers.add_parser(
102 parser_create = subparsers.add_parser(
101 'create',
103 'create',
102 help='Create a new cluster directory.',
104 help='Create a new cluster directory.',
103 parents=[parent_parser1, parent_parser2]
105 parents=[parent_parser1, parent_parser2]
104 )
106 )
105 parser_create.add_argument(
107 parser_create.add_argument(
106 '--reset-config',
108 '--reset-config',
107 dest='Global.reset_config', action='store_true',
109 dest='Global.reset_config', action='store_true',
108 default=NoConfigDefault,
110 default=NoConfigDefault,
109 help='Recopy the default config files to the cluster directory. '
111 help='Recopy the default config files to the cluster directory. '
110 'You will loose any modifications you have made to these files.'
112 'You will loose any modifications you have made to these files.'
111 )
113 )
112
114
113 parser_start = subparsers.add_parser(
115 parser_start = subparsers.add_parser(
114 'start',
116 'start',
115 help='Start a cluster.',
117 help='Start a cluster.',
116 parents=[parent_parser1, parent_parser2]
118 parents=[parent_parser1, parent_parser2]
117 )
119 )
118 parser_start.add_argument(
120 parser_start.add_argument(
119 '-n', '--number',
121 '-n', '--number',
120 type=int, dest='Global.n',
122 type=int, dest='Global.n',
121 default=NoConfigDefault,
123 default=NoConfigDefault,
122 help='The number of engines to start.',
124 help='The number of engines to start.',
123 metavar='Global.n'
125 metavar='Global.n'
124 )
126 )
125 parser_start.add_argument('-clean_logs', '--clean-logs',
127 parser_start.add_argument('-clean_logs', '--clean-logs',
126 dest='Global.clean_logs', action='store_true',
128 dest='Global.clean_logs', action='store_true',
127 help='Delete old log flies before starting.',
129 help='Delete old log flies before starting.',
128 default=NoConfigDefault
130 default=NoConfigDefault
129 )
131 )
130 parser_start.add_argument('-noclean_logs', '--no-clean-logs',
132 parser_start.add_argument('-noclean_logs', '--no-clean-logs',
131 dest='Global.clean_logs', action='store_false',
133 dest='Global.clean_logs', action='store_false',
132 help="Don't delete old log flies before starting.",
134 help="Don't delete old log flies before starting.",
133 default=NoConfigDefault
135 default=NoConfigDefault
134 )
136 )
137 parser_start.add_argument('--daemon', '-daemon',
138 dest='Global.daemonize', action='store_true',
139 help='Daemonize the ipcluster program. This implies --log-to-file',
140 default=NoConfigDefault
141 )
142 parser_start.add_argument('--nodaemon', '-nodaemon',
143 dest='Global.daemonize', action='store_false',
144 help="Dont't daemonize the ipcluster program.",
145 default=NoConfigDefault
146 )
147
148 parser_start = subparsers.add_parser(
149 'stop',
150 help='Stop a cluster.',
151 parents=[parent_parser1, parent_parser2]
152 )
153 parser_start.add_argument('-sig', '--sig',
154 dest='Global.stop_signal', type=int,
155 help="The signal number to use in stopping the cluster (default=2).",
156 default=NoConfigDefault
157 )
135
158
136 default_config_file_name = 'ipcluster_config.py'
159 default_config_file_name = 'ipcluster_config.py'
137
160
138
161
139 class IPClusterApp(ApplicationWithClusterDir):
162 class IPClusterApp(ApplicationWithClusterDir):
140
163
141 name = 'ipcluster'
164 name = 'ipcluster'
142 description = 'Start an IPython cluster (controller and engines).'
165 description = 'Start an IPython cluster (controller and engines).'
143 config_file_name = default_config_file_name
166 config_file_name = default_config_file_name
144 default_log_level = logging.INFO
167 default_log_level = logging.INFO
145 auto_create_cluster_dir = False
168 auto_create_cluster_dir = False
146
169
147 def create_default_config(self):
170 def create_default_config(self):
148 super(IPClusterApp, self).create_default_config()
171 super(IPClusterApp, self).create_default_config()
149 self.default_config.Global.controller_launcher = \
172 self.default_config.Global.controller_launcher = \
150 'IPython.kernel.launcher.LocalControllerLauncher'
173 'IPython.kernel.launcher.LocalControllerLauncher'
151 self.default_config.Global.engine_launcher = \
174 self.default_config.Global.engine_launcher = \
152 'IPython.kernel.launcher.LocalEngineSetLauncher'
175 'IPython.kernel.launcher.LocalEngineSetLauncher'
153 self.default_config.Global.n = 2
176 self.default_config.Global.n = 2
154 self.default_config.Global.reset_config = False
177 self.default_config.Global.reset_config = False
155 self.default_config.Global.clean_logs = True
178 self.default_config.Global.clean_logs = True
179 self.default_config.Global.stop_signal = 2
180 self.default_config.Global.daemonize = False
156
181
157 def create_command_line_config(self):
182 def create_command_line_config(self):
158 """Create and return a command line config loader."""
183 """Create and return a command line config loader."""
159 return IPClusterCLLoader(
184 return IPClusterCLLoader(
160 description=self.description,
185 description=self.description,
161 version=release.version
186 version=release.version
162 )
187 )
163
188
164 def find_resources(self):
189 def find_resources(self):
165 subcommand = self.command_line_config.Global.subcommand
190 subcommand = self.command_line_config.Global.subcommand
166 if subcommand=='list':
191 if subcommand=='list':
167 self.list_cluster_dirs()
192 self.list_cluster_dirs()
168 # Exit immediately because there is nothing left to do.
193 # Exit immediately because there is nothing left to do.
169 self.exit()
194 self.exit()
170 elif subcommand=='create':
195 elif subcommand=='create':
171 self.auto_create_cluster_dir = True
196 self.auto_create_cluster_dir = True
172 super(IPClusterApp, self).find_resources()
197 super(IPClusterApp, self).find_resources()
173 elif subcommand=='start':
198 elif subcommand=='start' or subcommand=='stop':
174 self.auto_create_cluster_dir = False
199 self.auto_create_cluster_dir = False
175 try:
200 try:
176 super(IPClusterApp, self).find_resources()
201 super(IPClusterApp, self).find_resources()
177 except ClusterDirError:
202 except ClusterDirError:
178 raise ClusterDirError(
203 raise ClusterDirError(
179 "Could not find a cluster directory. A cluster dir must "
204 "Could not find a cluster directory. A cluster dir must "
180 "be created before running 'ipcluster start'. Do "
205 "be created before running 'ipcluster start'. Do "
181 "'ipcluster create -h' or 'ipcluster list -h' for more "
206 "'ipcluster create -h' or 'ipcluster list -h' for more "
182 "information about creating and listing cluster dirs."
207 "information about creating and listing cluster dirs."
183 )
208 )
184
209
210 def pre_construct(self):
211 super(IPClusterApp, self).pre_construct()
212 config = self.master_config
213 try:
214 daemon = config.Global.daemonize
215 if daemon:
216 config.Global.log_to_file = True
217 except AttributeError:
218 pass
219
185 def construct(self):
220 def construct(self):
186 config = self.master_config
221 config = self.master_config
187 if config.Global.subcommand=='list':
222 if config.Global.subcommand=='list':
188 pass
223 pass
189 elif config.Global.subcommand=='create':
224 elif config.Global.subcommand=='create':
190 self.log.info('Copying default config files to cluster directory '
225 self.log.info('Copying default config files to cluster directory '
191 '[overwrite=%r]' % (config.Global.reset_config,))
226 '[overwrite=%r]' % (config.Global.reset_config,))
192 self.cluster_dir_obj.copy_all_config_files(overwrite=config.Global.reset_config)
227 self.cluster_dir_obj.copy_all_config_files(overwrite=config.Global.reset_config)
193 elif config.Global.subcommand=='start':
228 elif config.Global.subcommand=='start':
194 self.start_logging()
229 self.start_logging()
195 reactor.callWhenRunning(self.start_launchers)
230 reactor.callWhenRunning(self.start_launchers)
196
231
197 def list_cluster_dirs(self):
232 def list_cluster_dirs(self):
198 # Find the search paths
233 # Find the search paths
199 cluster_dir_paths = os.environ.get('IPCLUSTERDIR_PATH','')
234 cluster_dir_paths = os.environ.get('IPCLUSTERDIR_PATH','')
200 if cluster_dir_paths:
235 if cluster_dir_paths:
201 cluster_dir_paths = cluster_dir_paths.split(':')
236 cluster_dir_paths = cluster_dir_paths.split(':')
202 else:
237 else:
203 cluster_dir_paths = []
238 cluster_dir_paths = []
204 try:
239 try:
205 ipythondir = self.command_line_config.Global.ipythondir
240 ipythondir = self.command_line_config.Global.ipythondir
206 except AttributeError:
241 except AttributeError:
207 ipythondir = self.default_config.Global.ipythondir
242 ipythondir = self.default_config.Global.ipythondir
208 paths = [os.getcwd(), ipythondir] + \
243 paths = [os.getcwd(), ipythondir] + \
209 cluster_dir_paths
244 cluster_dir_paths
210 paths = list(set(paths))
245 paths = list(set(paths))
211
246
212 self.log.info('Searching for cluster dirs in paths: %r' % paths)
247 self.log.info('Searching for cluster dirs in paths: %r' % paths)
213 for path in paths:
248 for path in paths:
214 files = os.listdir(path)
249 files = os.listdir(path)
215 for f in files:
250 for f in files:
216 full_path = os.path.join(path, f)
251 full_path = os.path.join(path, f)
217 if os.path.isdir(full_path) and f.startswith('cluster_'):
252 if os.path.isdir(full_path) and f.startswith('cluster_'):
218 profile = full_path.split('_')[-1]
253 profile = full_path.split('_')[-1]
219 start_cmd = '"ipcluster start -n 4 -p %s"' % profile
254 start_cmd = '"ipcluster start -n 4 -p %s"' % profile
220 print start_cmd + " ==> " + full_path
255 print start_cmd + " ==> " + full_path
221
256
222 def start_launchers(self):
257 def start_launchers(self):
223 config = self.master_config
258 config = self.master_config
224
259
225 # Create the launchers
260 # Create the launchers
226 el_class = import_item(config.Global.engine_launcher)
261 el_class = import_item(config.Global.engine_launcher)
227 self.engine_launcher = el_class(
262 self.engine_launcher = el_class(
228 self.cluster_dir, config=config
263 self.cluster_dir, config=config
229 )
264 )
230 cl_class = import_item(config.Global.controller_launcher)
265 cl_class = import_item(config.Global.controller_launcher)
231 self.controller_launcher = cl_class(
266 self.controller_launcher = cl_class(
232 self.cluster_dir, config=config
267 self.cluster_dir, config=config
233 )
268 )
234
269
235 # Setup signals
270 # Setup signals
236 signal.signal(signal.SIGINT, self.stop_launchers)
271 signal.signal(signal.SIGINT, self.stop_launchers)
237 # signal.signal(signal.SIGKILL, self.stop_launchers)
272 # signal.signal(signal.SIGKILL, self.stop_launchers)
238
273
239 # Setup the observing of stopping
274 # Setup the observing of stopping
240 d1 = self.controller_launcher.observe_stop()
275 d1 = self.controller_launcher.observe_stop()
241 d1.addCallback(self.stop_engines)
276 d1.addCallback(self.stop_engines)
242 d1.addErrback(self.err_and_stop)
277 d1.addErrback(self.err_and_stop)
243 # If this triggers, just let them die
278 # If this triggers, just let them die
244 # d2 = self.engine_launcher.observe_stop()
279 # d2 = self.engine_launcher.observe_stop()
245
280
246 # Start the controller and engines
281 # Start the controller and engines
247 d = self.controller_launcher.start(
282 d = self.controller_launcher.start(
248 profile=None, cluster_dir=config.Global.cluster_dir
283 profile=None, cluster_dir=config.Global.cluster_dir
249 )
284 )
250 d.addCallback(lambda _: self.start_engines())
285 d.addCallback(lambda _: self.start_engines())
251 d.addErrback(self.err_and_stop)
286 d.addErrback(self.err_and_stop)
252
287
253 def err_and_stop(self, f):
288 def err_and_stop(self, f):
254 log.msg('Unexpected error in ipcluster:')
289 log.msg('Unexpected error in ipcluster:')
255 log.err(f)
290 log.err(f)
256 reactor.stop()
291 reactor.stop()
257
292
258 def stop_engines(self, r):
293 def stop_engines(self, r):
259 return self.engine_launcher.stop()
294 return self.engine_launcher.stop()
260
295
261 def start_engines(self):
296 def start_engines(self):
262 config = self.master_config
297 config = self.master_config
263 d = self.engine_launcher.start(
298 d = self.engine_launcher.start(
264 config.Global.n,
299 config.Global.n,
265 profile=None, cluster_dir=config.Global.cluster_dir
300 profile=None, cluster_dir=config.Global.cluster_dir
266 )
301 )
267 return d
302 return d
268
303
269 def stop_launchers(self, signum, frame):
304 def stop_launchers(self, signum, frame):
270 log.msg("Stopping cluster")
305 log.msg("Stopping cluster")
271 d1 = self.engine_launcher.stop()
306 d1 = self.engine_launcher.stop()
272 d2 = self.controller_launcher.stop()
307 d2 = self.controller_launcher.stop()
273 # d1.addCallback(lambda _: self.controller_launcher.stop)
308 # d1.addCallback(lambda _: self.controller_launcher.stop)
274 d1.addErrback(self.err_and_stop)
309 d1.addErrback(self.err_and_stop)
275 d2.addErrback(self.err_and_stop)
310 d2.addErrback(self.err_and_stop)
276 reactor.callLater(2.0, reactor.stop)
311 reactor.callLater(2.0, reactor.stop)
277
312
278 def start_logging(self):
313 def start_logging(self):
279 # Remove old log files
314 # Remove old log files
280 if self.master_config.Global.clean_logs:
315 if self.master_config.Global.clean_logs:
281 log_dir = self.master_config.Global.log_dir
316 log_dir = self.master_config.Global.log_dir
282 for f in os.listdir(log_dir):
317 for f in os.listdir(log_dir):
283 if f.startswith('ipengine' + '-') and f.endswith('.log'):
318 if f.startswith('ipengine' + '-') and f.endswith('.log'):
284 os.remove(os.path.join(log_dir, f))
319 os.remove(os.path.join(log_dir, f))
285 for f in os.listdir(log_dir):
320 for f in os.listdir(log_dir):
286 if f.startswith('ipcontroller' + '-') and f.endswith('.log'):
321 if f.startswith('ipcontroller' + '-') and f.endswith('.log'):
287 os.remove(os.path.join(log_dir, f))
322 os.remove(os.path.join(log_dir, f))
288 super(IPClusterApp, self).start_logging()
323 super(IPClusterApp, self).start_logging()
289
324
290 def start_app(self):
325 def start_app(self):
326 """Start the application, depending on what subcommand is used."""
291 config = self.master_config
327 config = self.master_config
292 if config.Global.subcommand=='create' or config.Global.subcommand=='list':
328 subcmd = config.Global.subcommand
329 if subcmd=='create' or subcmd=='list':
293 return
330 return
294 elif config.Global.subcommand=='start':
331 elif subcmd=='start':
332 # First see if the cluster is already running
333 try:
334 pid = self.get_pid_from_file()
335 except:
336 pass
337 else:
338 self.log.critical(
339 'Cluster is already running with [pid=%s]. '
340 'use "ipcluster stop" to stop the cluster.' % pid
341 )
342 sys.exit(9)
343 # Now log and daemonize
344 self.log.info('Starting ipcluster with [daemon=%r]' % config.Global.daemonize)
345 if config.Global.daemonize:
346 if os.name=='posix':
347 os.chdir(config.Global.cluster_dir)
348 self.log_level = 40
349 daemonize()
350
351 # Now write the new pid file after our new forked pid is active.
352 self.write_pid_file()
353 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
295 reactor.run()
354 reactor.run()
355 elif subcmd=='stop':
356 try:
357 pid = self.get_pid_from_file()
358 except PIDFileError:
359 self.log.critical(
360 'Problem reading pid file, cluster is probably not running.'
361 )
362 sys.exit(9)
363 sig = config.Global.stop_signal
364 self.log.info(
365 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
366 )
367 os.kill(pid, sig)
296
368
297
369
298 def launch_new_instance():
370 def launch_new_instance():
299 """Create and run the IPython cluster."""
371 """Create and run the IPython cluster."""
300 app = IPClusterApp()
372 app = IPClusterApp()
301 app.start()
373 app.start()
302
374
303
375
304 if __name__ == '__main__':
376 if __name__ == '__main__':
305 launch_new_instance()
377 launch_new_instance()
306
378
@@ -1,254 +1,258 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller application.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
19
18 import copy
20 import copy
19 import os
21 import os
20 import sys
22 import sys
21
23
22 from twisted.application import service
24 from twisted.application import service
23 from twisted.internet import reactor
25 from twisted.internet import reactor
24 from twisted.python import log
26 from twisted.python import log
25
27
26 from IPython.config.loader import Config, NoConfigDefault
28 from IPython.config.loader import Config, NoConfigDefault
27
29
28 from IPython.kernel.clusterdir import (
30 from IPython.kernel.clusterdir import (
29 ApplicationWithClusterDir,
31 ApplicationWithClusterDir,
30 AppWithClusterDirArgParseConfigLoader
32 AppWithClusterDirArgParseConfigLoader
31 )
33 )
32
34
33 from IPython.core import release
35 from IPython.core import release
34
36
35 from IPython.utils.traitlets import Str, Instance
37 from IPython.utils.traitlets import Str, Instance
36
38
37 from IPython.kernel import controllerservice
39 from IPython.kernel import controllerservice
38
40
39 from IPython.kernel.fcutil import FCServiceFactory
41 from IPython.kernel.fcutil import FCServiceFactory
40
42
41 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
42 # Default interfaces
44 # Default interfaces
43 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
44
46
45
47
46 # The default client interfaces for FCClientServiceFactory.interfaces
48 # The default client interfaces for FCClientServiceFactory.interfaces
47 default_client_interfaces = Config()
49 default_client_interfaces = Config()
48 default_client_interfaces.Task.interface_chain = [
50 default_client_interfaces.Task.interface_chain = [
49 'IPython.kernel.task.ITaskController',
51 'IPython.kernel.task.ITaskController',
50 'IPython.kernel.taskfc.IFCTaskController'
52 'IPython.kernel.taskfc.IFCTaskController'
51 ]
53 ]
52
54
53 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
55 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
54
56
55 default_client_interfaces.MultiEngine.interface_chain = [
57 default_client_interfaces.MultiEngine.interface_chain = [
56 'IPython.kernel.multiengine.IMultiEngine',
58 'IPython.kernel.multiengine.IMultiEngine',
57 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
59 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
58 ]
60 ]
59
61
60 default_client_interfaces.MultiEngine.furl_file = 'ipcontroller-mec.furl'
62 default_client_interfaces.MultiEngine.furl_file = 'ipcontroller-mec.furl'
61
63
62 # Make this a dict we can pass to Config.__init__ for the default
64 # Make this a dict we can pass to Config.__init__ for the default
63 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
65 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
64
66
65
67
66
68
67 # The default engine interfaces for FCEngineServiceFactory.interfaces
69 # The default engine interfaces for FCEngineServiceFactory.interfaces
68 default_engine_interfaces = Config()
70 default_engine_interfaces = Config()
69 default_engine_interfaces.Default.interface_chain = [
71 default_engine_interfaces.Default.interface_chain = [
70 'IPython.kernel.enginefc.IFCControllerBase'
72 'IPython.kernel.enginefc.IFCControllerBase'
71 ]
73 ]
72
74
73 default_engine_interfaces.Default.furl_file = 'ipcontroller-engine.furl'
75 default_engine_interfaces.Default.furl_file = 'ipcontroller-engine.furl'
74
76
75 # Make this a dict we can pass to Config.__init__ for the default
77 # Make this a dict we can pass to Config.__init__ for the default
76 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
78 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
77
79
78
80
79 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
80 # Service factories
82 # Service factories
81 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
82
84
83
85
84 class FCClientServiceFactory(FCServiceFactory):
86 class FCClientServiceFactory(FCServiceFactory):
85 """A Foolscap implementation of the client services."""
87 """A Foolscap implementation of the client services."""
86
88
87 cert_file = Str('ipcontroller-client.pem', config=True)
89 cert_file = Str('ipcontroller-client.pem', config=True)
88 interfaces = Instance(klass=Config, kw=default_client_interfaces,
90 interfaces = Instance(klass=Config, kw=default_client_interfaces,
89 allow_none=False, config=True)
91 allow_none=False, config=True)
90
92
91
93
92 class FCEngineServiceFactory(FCServiceFactory):
94 class FCEngineServiceFactory(FCServiceFactory):
93 """A Foolscap implementation of the engine services."""
95 """A Foolscap implementation of the engine services."""
94
96
95 cert_file = Str('ipcontroller-engine.pem', config=True)
97 cert_file = Str('ipcontroller-engine.pem', config=True)
96 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
98 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
97 allow_none=False, config=True)
99 allow_none=False, config=True)
98
100
99
101
100 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
101 # The main application
103 # The main application
102 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
103
105
104
106
105 cl_args = (
107 cl_args = (
106 # Client config
108 # Client config
107 (('--client-ip',), dict(
109 (('--client-ip',), dict(
108 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
110 type=str, dest='FCClientServiceFactory.ip', default=NoConfigDefault,
109 help='The IP address or hostname the controller will listen on for '
111 help='The IP address or hostname the controller will listen on for '
110 'client connections.',
112 'client connections.',
111 metavar='FCClientServiceFactory.ip')
113 metavar='FCClientServiceFactory.ip')
112 ),
114 ),
113 (('--client-port',), dict(
115 (('--client-port',), dict(
114 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
116 type=int, dest='FCClientServiceFactory.port', default=NoConfigDefault,
115 help='The port the controller will listen on for client connections. '
117 help='The port the controller will listen on for client connections. '
116 'The default is to use 0, which will autoselect an open port.',
118 'The default is to use 0, which will autoselect an open port.',
117 metavar='FCClientServiceFactory.port')
119 metavar='FCClientServiceFactory.port')
118 ),
120 ),
119 (('--client-location',), dict(
121 (('--client-location',), dict(
120 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
122 type=str, dest='FCClientServiceFactory.location', default=NoConfigDefault,
121 help='The hostname or IP that clients should connect to. This does '
123 help='The hostname or IP that clients should connect to. This does '
122 'not control which interface the controller listens on. Instead, this '
124 'not control which interface the controller listens on. Instead, this '
123 'determines the hostname/IP that is listed in the FURL, which is how '
125 'determines the hostname/IP that is listed in the FURL, which is how '
124 'clients know where to connect. Useful if the controller is listening '
126 'clients know where to connect. Useful if the controller is listening '
125 'on multiple interfaces.',
127 'on multiple interfaces.',
126 metavar='FCClientServiceFactory.location')
128 metavar='FCClientServiceFactory.location')
127 ),
129 ),
128 # Engine config
130 # Engine config
129 (('--engine-ip',), dict(
131 (('--engine-ip',), dict(
130 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
132 type=str, dest='FCEngineServiceFactory.ip', default=NoConfigDefault,
131 help='The IP address or hostname the controller will listen on for '
133 help='The IP address or hostname the controller will listen on for '
132 'engine connections.',
134 'engine connections.',
133 metavar='FCEngineServiceFactory.ip')
135 metavar='FCEngineServiceFactory.ip')
134 ),
136 ),
135 (('--engine-port',), dict(
137 (('--engine-port',), dict(
136 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
138 type=int, dest='FCEngineServiceFactory.port', default=NoConfigDefault,
137 help='The port the controller will listen on for engine connections. '
139 help='The port the controller will listen on for engine connections. '
138 'The default is to use 0, which will autoselect an open port.',
140 'The default is to use 0, which will autoselect an open port.',
139 metavar='FCEngineServiceFactory.port')
141 metavar='FCEngineServiceFactory.port')
140 ),
142 ),
141 (('--engine-location',), dict(
143 (('--engine-location',), dict(
142 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
144 type=str, dest='FCEngineServiceFactory.location', default=NoConfigDefault,
143 help='The hostname or IP that engines should connect to. This does '
145 help='The hostname or IP that engines should connect to. This does '
144 'not control which interface the controller listens on. Instead, this '
146 'not control which interface the controller listens on. Instead, this '
145 'determines the hostname/IP that is listed in the FURL, which is how '
147 'determines the hostname/IP that is listed in the FURL, which is how '
146 'engines know where to connect. Useful if the controller is listening '
148 'engines know where to connect. Useful if the controller is listening '
147 'on multiple interfaces.',
149 'on multiple interfaces.',
148 metavar='FCEngineServiceFactory.location')
150 metavar='FCEngineServiceFactory.location')
149 ),
151 ),
150 # Global config
152 # Global config
151 (('--log-to-file',), dict(
153 (('--log-to-file',), dict(
152 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
154 action='store_true', dest='Global.log_to_file', default=NoConfigDefault,
153 help='Log to a file in the log directory (default is stdout)')
155 help='Log to a file in the log directory (default is stdout)')
154 ),
156 ),
155 (('-r','--reuse-furls'), dict(
157 (('-r','--reuse-furls'), dict(
156 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
158 action='store_true', dest='Global.reuse_furls', default=NoConfigDefault,
157 help='Try to reuse all FURL files. If this is not set all FURL files '
159 help='Try to reuse all FURL files. If this is not set all FURL files '
158 'are deleted before the controller starts. This must be set if '
160 'are deleted before the controller starts. This must be set if '
159 'specific ports are specified by --engine-port or --client-port.')
161 'specific ports are specified by --engine-port or --client-port.')
160 ),
162 ),
161 (('-ns','--no-security'), dict(
163 (('-ns','--no-security'), dict(
162 action='store_false', dest='Global.secure', default=NoConfigDefault,
164 action='store_false', dest='Global.secure', default=NoConfigDefault,
163 help='Turn off SSL encryption for all connections.')
165 help='Turn off SSL encryption for all connections.')
164 )
166 )
165 )
167 )
166
168
167
169
168 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
170 class IPControllerAppCLConfigLoader(AppWithClusterDirArgParseConfigLoader):
169
171
170 arguments = cl_args
172 arguments = cl_args
171
173
172
174
173 default_config_file_name = 'ipcontroller_config.py'
175 default_config_file_name = 'ipcontroller_config.py'
174
176
175
177
176 class IPControllerApp(ApplicationWithClusterDir):
178 class IPControllerApp(ApplicationWithClusterDir):
177
179
178 name = 'ipcontroller'
180 name = 'ipcontroller'
179 description = 'Start the IPython controller for parallel computing.'
181 description = 'Start the IPython controller for parallel computing.'
180 config_file_name = default_config_file_name
182 config_file_name = default_config_file_name
181 auto_create_cluster_dir = True
183 auto_create_cluster_dir = True
182
184
183 def create_default_config(self):
185 def create_default_config(self):
184 super(IPControllerApp, self).create_default_config()
186 super(IPControllerApp, self).create_default_config()
185 self.default_config.Global.reuse_furls = False
187 self.default_config.Global.reuse_furls = False
186 self.default_config.Global.secure = True
188 self.default_config.Global.secure = True
187 self.default_config.Global.import_statements = []
189 self.default_config.Global.import_statements = []
188 self.default_config.Global.clean_logs = True
190 self.default_config.Global.clean_logs = True
189
191
190 def create_command_line_config(self):
192 def create_command_line_config(self):
191 """Create and return a command line config loader."""
193 """Create and return a command line config loader."""
192 return IPControllerAppCLConfigLoader(
194 return IPControllerAppCLConfigLoader(
193 description=self.description,
195 description=self.description,
194 version=release.version
196 version=release.version
195 )
197 )
196
198
197 def post_load_command_line_config(self):
199 def post_load_command_line_config(self):
198 # Now setup reuse_furls
200 # Now setup reuse_furls
199 c = self.command_line_config
201 c = self.command_line_config
200 if hasattr(c.Global, 'reuse_furls'):
202 if hasattr(c.Global, 'reuse_furls'):
201 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
203 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
202 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
204 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
203 del c.Global.reuse_furls
205 del c.Global.reuse_furls
204 if hasattr(c.Global, 'secure'):
206 if hasattr(c.Global, 'secure'):
205 c.FCClientServiceFactory.secure = c.Global.secure
207 c.FCClientServiceFactory.secure = c.Global.secure
206 c.FCEngineServiceFactory.secure = c.Global.secure
208 c.FCEngineServiceFactory.secure = c.Global.secure
207 del c.Global.secure
209 del c.Global.secure
208
210
209 def construct(self):
211 def construct(self):
210 # I am a little hesitant to put these into InteractiveShell itself.
212 # I am a little hesitant to put these into InteractiveShell itself.
211 # But that might be the place for them
213 # But that might be the place for them
212 sys.path.insert(0, '')
214 sys.path.insert(0, '')
213
215
214 self.start_logging()
216 self.start_logging()
215 self.import_statements()
217 self.import_statements()
216
218
217 # Create the service hierarchy
219 # Create the service hierarchy
218 self.main_service = service.MultiService()
220 self.main_service = service.MultiService()
219 # The controller service
221 # The controller service
220 controller_service = controllerservice.ControllerService()
222 controller_service = controllerservice.ControllerService()
221 controller_service.setServiceParent(self.main_service)
223 controller_service.setServiceParent(self.main_service)
222 # The client tub and all its refereceables
224 # The client tub and all its refereceables
223 csfactory = FCClientServiceFactory(self.master_config, controller_service)
225 csfactory = FCClientServiceFactory(self.master_config, controller_service)
224 client_service = csfactory.create()
226 client_service = csfactory.create()
225 client_service.setServiceParent(self.main_service)
227 client_service.setServiceParent(self.main_service)
226 # The engine tub
228 # The engine tub
227 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
229 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
228 engine_service = esfactory.create()
230 engine_service = esfactory.create()
229 engine_service.setServiceParent(self.main_service)
231 engine_service.setServiceParent(self.main_service)
230
232
231 def import_statements(self):
233 def import_statements(self):
232 statements = self.master_config.Global.import_statements
234 statements = self.master_config.Global.import_statements
233 for s in statements:
235 for s in statements:
234 try:
236 try:
235 log.msg("Executing statement: '%s'" % s)
237 log.msg("Executing statement: '%s'" % s)
236 exec s in globals(), locals()
238 exec s in globals(), locals()
237 except:
239 except:
238 log.msg("Error running statement: %s" % s)
240 log.msg("Error running statement: %s" % s)
239
241
240 def start_app(self):
242 def start_app(self):
241 # Start the controller service and set things running
243 # Start the controller service and set things running
242 self.main_service.startService()
244 self.main_service.startService()
245 self.write_pid_file()
246 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
243 reactor.run()
247 reactor.run()
244
248
245
249
246 def launch_new_instance():
250 def launch_new_instance():
247 """Create and run the IPython controller"""
251 """Create and run the IPython controller"""
248 app = IPControllerApp()
252 app = IPControllerApp()
249 app.start()
253 app.start()
250
254
251
255
252 if __name__ == '__main__':
256 if __name__ == '__main__':
253 launch_new_instance()
257 launch_new_instance()
254
258
General Comments 0
You need to be logged in to leave comments. Login now