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