##// END OF EJS Templates
Removed IPython.config.default from the default search path for ipcluster...
Brian Granger -
Show More
@@ -1,493 +1,493 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython cluster directory
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import with_statement
19 19
20 20 import os
21 21 import shutil
22 22 import sys
23 23 import warnings
24 24
25 25 from twisted.python import log
26 26
27 27 from IPython.config.loader import PyFileConfigLoader
28 28 from IPython.core.application import Application, BaseAppConfigLoader
29 29 from IPython.core.component import Component
30 30 from IPython.utils.path import (
31 31 get_ipython_package_dir,
32 32 expand_path
33 33 )
34 34 from IPython.utils.traitlets import Unicode
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Warnings control
38 38 #-----------------------------------------------------------------------------
39 39 # Twisted generates annoying warnings with Python 2.6, as will do other code
40 40 # that imports 'sets' as of today
41 41 warnings.filterwarnings('ignore', 'the sets module is deprecated',
42 42 DeprecationWarning )
43 43
44 44 # This one also comes from Twisted
45 45 warnings.filterwarnings('ignore', 'the sha module is deprecated',
46 46 DeprecationWarning)
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Module errors
50 50 #-----------------------------------------------------------------------------
51 51
52 52 class ClusterDirError(Exception):
53 53 pass
54 54
55 55
56 56 class PIDFileError(Exception):
57 57 pass
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Class for managing cluster directories
62 62 #-----------------------------------------------------------------------------
63 63
64 64 class ClusterDir(Component):
65 65 """An object to manage the cluster directory and its resources.
66 66
67 67 The cluster directory is used by :command:`ipcontroller`,
68 68 :command:`ipcontroller` and :command:`ipcontroller` to manage the
69 69 configuration, logging and security of these applications.
70 70
71 71 This object knows how to find, create and manage these directories. This
72 72 should be used by any code that want's to handle cluster directories.
73 73 """
74 74
75 75 security_dir_name = Unicode('security')
76 76 log_dir_name = Unicode('log')
77 77 pid_dir_name = Unicode('pid')
78 78 security_dir = Unicode(u'')
79 79 log_dir = Unicode(u'')
80 80 pid_dir = Unicode(u'')
81 81 location = Unicode(u'')
82 82
83 83 def __init__(self, location):
84 84 super(ClusterDir, self).__init__(None)
85 85 self.location = location
86 86
87 87 def _location_changed(self, name, old, new):
88 88 if not os.path.isdir(new):
89 89 os.makedirs(new)
90 90 self.security_dir = os.path.join(new, self.security_dir_name)
91 91 self.log_dir = os.path.join(new, self.log_dir_name)
92 92 self.pid_dir = os.path.join(new, self.pid_dir_name)
93 93 self.check_dirs()
94 94
95 95 def _log_dir_changed(self, name, old, new):
96 96 self.check_log_dir()
97 97
98 98 def check_log_dir(self):
99 99 if not os.path.isdir(self.log_dir):
100 100 os.mkdir(self.log_dir)
101 101
102 102 def _security_dir_changed(self, name, old, new):
103 103 self.check_security_dir()
104 104
105 105 def check_security_dir(self):
106 106 if not os.path.isdir(self.security_dir):
107 107 os.mkdir(self.security_dir, 0700)
108 108 os.chmod(self.security_dir, 0700)
109 109
110 110 def _pid_dir_changed(self, name, old, new):
111 111 self.check_pid_dir()
112 112
113 113 def check_pid_dir(self):
114 114 if not os.path.isdir(self.pid_dir):
115 115 os.mkdir(self.pid_dir, 0700)
116 116 os.chmod(self.pid_dir, 0700)
117 117
118 118 def check_dirs(self):
119 119 self.check_security_dir()
120 120 self.check_log_dir()
121 121 self.check_pid_dir()
122 122
123 123 def load_config_file(self, filename):
124 124 """Load a config file from the top level of the cluster dir.
125 125
126 126 Parameters
127 127 ----------
128 128 filename : unicode or str
129 129 The filename only of the config file that must be located in
130 130 the top-level of the cluster directory.
131 131 """
132 132 loader = PyFileConfigLoader(filename, self.location)
133 133 return loader.load_config()
134 134
135 135 def copy_config_file(self, config_file, path=None, overwrite=False):
136 136 """Copy a default config file into the active cluster directory.
137 137
138 138 Default configuration files are kept in :mod:`IPython.config.default`.
139 139 This function moves these from that location to the working cluster
140 140 directory.
141 141 """
142 142 if path is None:
143 143 import IPython.config.default
144 144 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
145 145 path = os.path.sep.join(path)
146 146 src = os.path.join(path, config_file)
147 147 dst = os.path.join(self.location, config_file)
148 148 if not os.path.isfile(dst) or overwrite:
149 149 shutil.copy(src, dst)
150 150
151 151 def copy_all_config_files(self, path=None, overwrite=False):
152 152 """Copy all config files into the active cluster directory."""
153 153 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
154 154 u'ipcluster_config.py']:
155 155 self.copy_config_file(f, path=path, overwrite=overwrite)
156 156
157 157 @classmethod
158 158 def create_cluster_dir(csl, cluster_dir):
159 159 """Create a new cluster directory given a full path.
160 160
161 161 Parameters
162 162 ----------
163 163 cluster_dir : str
164 164 The full path to the cluster directory. If it does exist, it will
165 165 be used. If not, it will be created.
166 166 """
167 167 return ClusterDir(cluster_dir)
168 168
169 169 @classmethod
170 170 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
171 171 """Create a cluster dir by profile name and path.
172 172
173 173 Parameters
174 174 ----------
175 175 path : str
176 176 The path (directory) to put the cluster directory in.
177 177 profile : str
178 178 The name of the profile. The name of the cluster directory will
179 179 be "cluster_<profile>".
180 180 """
181 181 if not os.path.isdir(path):
182 182 raise ClusterDirError('Directory not found: %s' % path)
183 183 cluster_dir = os.path.join(path, u'cluster_' + profile)
184 184 return ClusterDir(cluster_dir)
185 185
186 186 @classmethod
187 187 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
188 188 """Find an existing cluster dir by profile name, return its ClusterDir.
189 189
190 190 This searches through a sequence of paths for a cluster dir. If it
191 191 is not found, a :class:`ClusterDirError` exception will be raised.
192 192
193 193 The search path algorithm is:
194 194 1. ``os.getcwd()``
195 195 2. ``ipython_dir``
196 196 3. The directories found in the ":" separated
197 197 :env:`IPCLUSTER_DIR_PATH` environment variable.
198 198
199 199 Parameters
200 200 ----------
201 201 ipython_dir : unicode or str
202 202 The IPython directory to use.
203 203 profile : unicode or str
204 204 The name of the profile. The name of the cluster directory
205 205 will be "cluster_<profile>".
206 206 """
207 207 dirname = u'cluster_' + profile
208 208 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
209 209 if cluster_dir_paths:
210 210 cluster_dir_paths = cluster_dir_paths.split(':')
211 211 else:
212 212 cluster_dir_paths = []
213 213 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
214 214 for p in paths:
215 215 cluster_dir = os.path.join(p, dirname)
216 216 if os.path.isdir(cluster_dir):
217 217 return ClusterDir(cluster_dir)
218 218 else:
219 219 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
220 220
221 221 @classmethod
222 222 def find_cluster_dir(cls, cluster_dir):
223 223 """Find/create a cluster dir and return its ClusterDir.
224 224
225 225 This will create the cluster directory if it doesn't exist.
226 226
227 227 Parameters
228 228 ----------
229 229 cluster_dir : unicode or str
230 230 The path of the cluster directory. This is expanded using
231 231 :func:`IPython.utils.genutils.expand_path`.
232 232 """
233 233 cluster_dir = expand_path(cluster_dir)
234 234 if not os.path.isdir(cluster_dir):
235 235 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
236 236 return ClusterDir(cluster_dir)
237 237
238 238
239 239 #-----------------------------------------------------------------------------
240 240 # Command line options
241 241 #-----------------------------------------------------------------------------
242 242
243 243 class ClusterDirConfigLoader(BaseAppConfigLoader):
244 244
245 245 def _add_cluster_profile(self, parser):
246 246 paa = parser.add_argument
247 247 paa('-p', '--profile',
248 248 dest='Global.profile',type=unicode,
249 249 help=
250 250 """The string name of the profile to be used. This determines the name
251 251 of the cluster dir as: cluster_<profile>. The default profile is named
252 252 'default'. The cluster directory is resolve this way if the
253 253 --cluster-dir option is not used.""",
254 254 metavar='Global.profile')
255 255
256 256 def _add_cluster_dir(self, parser):
257 257 paa = parser.add_argument
258 258 paa('--cluster-dir',
259 259 dest='Global.cluster_dir',type=unicode,
260 260 help="""Set the cluster dir. This overrides the logic used by the
261 261 --profile option.""",
262 262 metavar='Global.cluster_dir')
263 263
264 264 def _add_work_dir(self, parser):
265 265 paa = parser.add_argument
266 266 paa('--work-dir',
267 267 dest='Global.work_dir',type=unicode,
268 268 help='Set the working dir for the process.',
269 269 metavar='Global.work_dir')
270 270
271 271 def _add_clean_logs(self, parser):
272 272 paa = parser.add_argument
273 273 paa('--clean-logs',
274 274 dest='Global.clean_logs', action='store_true',
275 275 help='Delete old log flies before starting.')
276 276
277 277 def _add_no_clean_logs(self, parser):
278 278 paa = parser.add_argument
279 279 paa('--no-clean-logs',
280 280 dest='Global.clean_logs', action='store_false',
281 281 help="Don't Delete old log flies before starting.")
282 282
283 283 def _add_arguments(self):
284 284 super(ClusterDirConfigLoader, self)._add_arguments()
285 285 self._add_cluster_profile(self.parser)
286 286 self._add_cluster_dir(self.parser)
287 287 self._add_work_dir(self.parser)
288 288 self._add_clean_logs(self.parser)
289 289 self._add_no_clean_logs(self.parser)
290 290
291 291
292 292 #-----------------------------------------------------------------------------
293 293 # Main application
294 294 #-----------------------------------------------------------------------------
295 295
296 296 class ApplicationWithClusterDir(Application):
297 297 """An application that puts everything into a cluster directory.
298 298
299 299 Instead of looking for things in the ipython_dir, this type of application
300 300 will use its own private directory called the "cluster directory"
301 301 for things like config files, log files, etc.
302 302
303 303 The cluster directory is resolved as follows:
304 304
305 305 * If the ``--cluster-dir`` option is given, it is used.
306 306 * If ``--cluster-dir`` is not given, the application directory is
307 307 resolve using the profile name as ``cluster_<profile>``. The search
308 308 path for this directory is then i) cwd if it is found there
309 309 and ii) in ipython_dir otherwise.
310 310
311 311 The config file for the application is to be put in the cluster
312 312 dir and named the value of the ``config_file_name`` class attribute.
313 313 """
314 314
315 315 command_line_loader = ClusterDirConfigLoader
316 316 auto_create_cluster_dir = True
317 317
318 318 def create_default_config(self):
319 319 super(ApplicationWithClusterDir, self).create_default_config()
320 320 self.default_config.Global.profile = u'default'
321 321 self.default_config.Global.cluster_dir = u''
322 322 self.default_config.Global.work_dir = os.getcwd()
323 323 self.default_config.Global.log_to_file = False
324 324 self.default_config.Global.clean_logs = False
325 325
326 326 def find_resources(self):
327 327 """This resolves the cluster directory.
328 328
329 329 This tries to find the cluster directory and if successful, it will
330 330 have done:
331 331 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
332 332 the application.
333 333 * Sets ``self.cluster_dir`` attribute of the application and config
334 334 objects.
335 335
336 336 The algorithm used for this is as follows:
337 337 1. Try ``Global.cluster_dir``.
338 338 2. Try using ``Global.profile``.
339 339 3. If both of these fail and ``self.auto_create_cluster_dir`` is
340 340 ``True``, then create the new cluster dir in the IPython directory.
341 341 4. If all fails, then raise :class:`ClusterDirError`.
342 342 """
343 343
344 344 try:
345 345 cluster_dir = self.command_line_config.Global.cluster_dir
346 346 except AttributeError:
347 347 cluster_dir = self.default_config.Global.cluster_dir
348 348 cluster_dir = expand_path(cluster_dir)
349 349 try:
350 350 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
351 351 except ClusterDirError:
352 352 pass
353 353 else:
354 354 self.log.info('Using existing cluster dir: %s' % \
355 355 self.cluster_dir_obj.location
356 356 )
357 357 self.finish_cluster_dir()
358 358 return
359 359
360 360 try:
361 361 self.profile = self.command_line_config.Global.profile
362 362 except AttributeError:
363 363 self.profile = self.default_config.Global.profile
364 364 try:
365 365 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
366 366 self.ipython_dir, self.profile)
367 367 except ClusterDirError:
368 368 pass
369 369 else:
370 370 self.log.info('Using existing cluster dir: %s' % \
371 371 self.cluster_dir_obj.location
372 372 )
373 373 self.finish_cluster_dir()
374 374 return
375 375
376 376 if self.auto_create_cluster_dir:
377 377 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
378 378 self.ipython_dir, self.profile
379 379 )
380 380 self.log.info('Creating new cluster dir: %s' % \
381 381 self.cluster_dir_obj.location
382 382 )
383 383 self.finish_cluster_dir()
384 384 else:
385 385 raise ClusterDirError('Could not find a valid cluster directory.')
386 386
387 387 def finish_cluster_dir(self):
388 388 # Set the cluster directory
389 389 self.cluster_dir = self.cluster_dir_obj.location
390 390
391 391 # These have to be set because they could be different from the one
392 392 # that we just computed. Because command line has the highest
393 393 # priority, this will always end up in the master_config.
394 394 self.default_config.Global.cluster_dir = self.cluster_dir
395 395 self.command_line_config.Global.cluster_dir = self.cluster_dir
396 396
397 397 def find_config_file_name(self):
398 398 """Find the config file name for this application."""
399 399 # For this type of Application it should be set as a class attribute.
400 400 if not hasattr(self, 'config_file_name'):
401 401 self.log.critical("No config filename found")
402 402
403 403 def find_config_file_paths(self):
404 # Include our own config directory last, so that users can still find
405 # our shipped copies of builtin config files even if they don't have
406 # them in their ipython cluster directory.
404 # Set the search path to to the cluster directory. We should NOT
405 # include IPython.config.default here as the default config files
406 # are ALWAYS automatically moved to the cluster directory.
407 407 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
408 self.config_file_paths = (self.cluster_dir, conf_dir)
408 self.config_file_paths = (self.cluster_dir,)
409 409
410 410 def pre_construct(self):
411 411 # The log and security dirs were set earlier, but here we put them
412 412 # into the config and log them.
413 413 config = self.master_config
414 414 sdir = self.cluster_dir_obj.security_dir
415 415 self.security_dir = config.Global.security_dir = sdir
416 416 ldir = self.cluster_dir_obj.log_dir
417 417 self.log_dir = config.Global.log_dir = ldir
418 418 pdir = self.cluster_dir_obj.pid_dir
419 419 self.pid_dir = config.Global.pid_dir = pdir
420 420 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
421 421 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
422 422 # Change to the working directory. We do this just before construct
423 423 # is called so all the components there have the right working dir.
424 424 self.to_work_dir()
425 425
426 426 def to_work_dir(self):
427 427 wd = self.master_config.Global.work_dir
428 428 if unicode(wd) != unicode(os.getcwd()):
429 429 os.chdir(wd)
430 430 self.log.info("Changing to working dir: %s" % wd)
431 431
432 432 def start_logging(self):
433 433 # Remove old log files
434 434 if self.master_config.Global.clean_logs:
435 435 log_dir = self.master_config.Global.log_dir
436 436 for f in os.listdir(log_dir):
437 437 if f.startswith(self.name + u'-') and f.endswith('.log'):
438 438 os.remove(os.path.join(log_dir, f))
439 439 # Start logging to the new log file
440 440 if self.master_config.Global.log_to_file:
441 441 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
442 442 logfile = os.path.join(self.log_dir, log_filename)
443 443 open_log_file = open(logfile, 'w')
444 444 else:
445 445 open_log_file = sys.stdout
446 446 log.startLogging(open_log_file)
447 447
448 448 def write_pid_file(self, overwrite=False):
449 449 """Create a .pid file in the pid_dir with my pid.
450 450
451 451 This must be called after pre_construct, which sets `self.pid_dir`.
452 452 This raises :exc:`PIDFileError` if the pid file exists already.
453 453 """
454 454 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
455 455 if os.path.isfile(pid_file):
456 456 pid = self.get_pid_from_file()
457 457 if not overwrite:
458 458 raise PIDFileError(
459 459 'The pid file [%s] already exists. \nThis could mean that this '
460 460 'server is already running with [pid=%s].' % (pid_file, pid)
461 461 )
462 462 with open(pid_file, 'w') as f:
463 463 self.log.info("Creating pid file: %s" % pid_file)
464 464 f.write(repr(os.getpid())+'\n')
465 465
466 466 def remove_pid_file(self):
467 467 """Remove the pid file.
468 468
469 469 This should be called at shutdown by registering a callback with
470 470 :func:`reactor.addSystemEventTrigger`. This needs to return
471 471 ``None``.
472 472 """
473 473 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
474 474 if os.path.isfile(pid_file):
475 475 try:
476 476 self.log.info("Removing pid file: %s" % pid_file)
477 477 os.remove(pid_file)
478 478 except:
479 479 self.log.warn("Error removing the pid file: %s" % pid_file)
480 480
481 481 def get_pid_from_file(self):
482 482 """Get the pid from the pid file.
483 483
484 484 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
485 485 """
486 486 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
487 487 if os.path.isfile(pid_file):
488 488 with open(pid_file, 'r') as f:
489 489 pid = int(f.read().strip())
490 490 return pid
491 491 else:
492 492 raise PIDFileError('pid file not found: %s' % pid_file)
493 493
General Comments 0
You need to be logged in to leave comments. Login now