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