##// END OF EJS Templates
Doing ipcluster stop on windows will now simply remove the .pid file.
Brian Granger -
Show More
@@ -1,406 +1,412 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
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 import logging
19 19 import os
20 20 import signal
21 21 import sys
22 22
23 23 if os.name=='posix':
24 24 from twisted.scripts._twistd_unix import daemonize
25 25
26 26 from IPython.core import release
27 27 from IPython.external import argparse
28 28 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
29 29 from IPython.utils.importstring import import_item
30 30
31 31 from IPython.kernel.clusterdir import (
32 32 ApplicationWithClusterDir, ClusterDirError, PIDFileError
33 33 )
34 34
35 35 from twisted.internet import reactor
36 36 from twisted.python import log
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # The ipcluster application
41 41 #-----------------------------------------------------------------------------
42 42
43 43
44 44 # Exit codes for ipcluster
45 45
46 46 # This will be the exit code if the ipcluster appears to be running because
47 47 # a .pid file exists
48 48 ALREADY_STARTED = 10
49 49
50 50 # This will be the exit code if ipcluster stop is run, but there is not .pid
51 51 # file to be found.
52 52 ALREADY_STOPPED = 11
53 53
54 54
55 55 class IPClusterCLLoader(ArgParseConfigLoader):
56 56
57 57 def _add_arguments(self):
58 58 # This has all the common options that all subcommands use
59 59 parent_parser1 = argparse.ArgumentParser(add_help=False)
60 60 parent_parser1.add_argument('--ipython-dir',
61 61 dest='Global.ipython_dir',type=unicode,
62 62 help='Set to override default location of Global.ipython_dir.',
63 63 default=NoConfigDefault,
64 64 metavar='Global.ipython_dir')
65 65 parent_parser1.add_argument('--log-level',
66 66 dest="Global.log_level",type=int,
67 67 help='Set the log level (0,10,20,30,40,50). Default is 30.',
68 68 default=NoConfigDefault,
69 69 metavar='Global.log_level')
70 70
71 71 # This has all the common options that other subcommands use
72 72 parent_parser2 = argparse.ArgumentParser(add_help=False)
73 73 parent_parser2.add_argument('-p','--profile',
74 74 dest='Global.profile',type=unicode,
75 75 default=NoConfigDefault,
76 76 help='The string name of the profile to be used. This determines '
77 77 'the name of the cluster dir as: cluster_<profile>. The default profile '
78 78 'is named "default". The cluster directory is resolve this way '
79 79 'if the --cluster-dir option is not used.',
80 80 default=NoConfigDefault,
81 81 metavar='Global.profile')
82 82 parent_parser2.add_argument('--cluster-dir',
83 83 dest='Global.cluster_dir',type=unicode,
84 84 default=NoConfigDefault,
85 85 help='Set the cluster dir. This overrides the logic used by the '
86 86 '--profile option.',
87 87 default=NoConfigDefault,
88 88 metavar='Global.cluster_dir'),
89 89 parent_parser2.add_argument('--working-dir',
90 90 dest='Global.working_dir',type=unicode,
91 91 help='Set the working dir for the process.',
92 92 default=NoConfigDefault,
93 93 metavar='Global.working_dir')
94 94 parent_parser2.add_argument('--log-to-file',
95 95 action='store_true', dest='Global.log_to_file',
96 96 default=NoConfigDefault,
97 97 help='Log to a file in the log directory (default is stdout)'
98 98 )
99 99
100 100 subparsers = self.parser.add_subparsers(
101 101 dest='Global.subcommand',
102 102 title='ipcluster subcommands',
103 103 description='ipcluster has a variety of subcommands. '
104 104 'The general way of running ipcluster is "ipcluster <cmd> '
105 105 ' [options]""',
106 106 help='For more help, type "ipcluster <cmd> -h"')
107 107
108 108 parser_list = subparsers.add_parser(
109 109 'list',
110 110 help='List all clusters in cwd and ipython_dir.',
111 111 parents=[parent_parser1]
112 112 )
113 113
114 114 parser_create = subparsers.add_parser(
115 115 'create',
116 116 help='Create a new cluster directory.',
117 117 parents=[parent_parser1, parent_parser2]
118 118 )
119 119 parser_create.add_argument(
120 120 '--reset-config',
121 121 dest='Global.reset_config', action='store_true',
122 122 default=NoConfigDefault,
123 123 help='Recopy the default config files to the cluster directory. '
124 124 'You will loose any modifications you have made to these files.'
125 125 )
126 126
127 127 parser_start = subparsers.add_parser(
128 128 'start',
129 129 help='Start a cluster.',
130 130 parents=[parent_parser1, parent_parser2]
131 131 )
132 132 parser_start.add_argument(
133 133 '-n', '--number',
134 134 type=int, dest='Global.n',
135 135 default=NoConfigDefault,
136 136 help='The number of engines to start.',
137 137 metavar='Global.n'
138 138 )
139 139 parser_start.add_argument('--clean-logs',
140 140 dest='Global.clean_logs', action='store_true',
141 141 help='Delete old log flies before starting.',
142 142 default=NoConfigDefault
143 143 )
144 144 parser_start.add_argument('--no-clean-logs',
145 145 dest='Global.clean_logs', action='store_false',
146 146 help="Don't delete old log flies before starting.",
147 147 default=NoConfigDefault
148 148 )
149 149 parser_start.add_argument('--daemon',
150 150 dest='Global.daemonize', action='store_true',
151 151 help='Daemonize the ipcluster program. This implies --log-to-file',
152 152 default=NoConfigDefault
153 153 )
154 154 parser_start.add_argument('--no-daemon',
155 155 dest='Global.daemonize', action='store_false',
156 156 help="Dont't daemonize the ipcluster program.",
157 157 default=NoConfigDefault
158 158 )
159 159
160 160 parser_start = subparsers.add_parser(
161 161 'stop',
162 162 help='Stop a cluster.',
163 163 parents=[parent_parser1, parent_parser2]
164 164 )
165 165 parser_start.add_argument('--signal',
166 166 dest='Global.signal', type=int,
167 167 help="The signal number to use in stopping the cluster (default=2).",
168 168 metavar="Global.signal",
169 169 default=NoConfigDefault
170 170 )
171 171
172 172
173 173 default_config_file_name = u'ipcluster_config.py'
174 174
175 175
176 176 class IPClusterApp(ApplicationWithClusterDir):
177 177
178 178 name = u'ipcluster'
179 179 description = 'Start an IPython cluster (controller and engines).'
180 180 config_file_name = default_config_file_name
181 181 default_log_level = logging.INFO
182 182 auto_create_cluster_dir = False
183 183
184 184 def create_default_config(self):
185 185 super(IPClusterApp, self).create_default_config()
186 186 self.default_config.Global.controller_launcher = \
187 187 'IPython.kernel.launcher.LocalControllerLauncher'
188 188 self.default_config.Global.engine_launcher = \
189 189 'IPython.kernel.launcher.LocalEngineSetLauncher'
190 190 self.default_config.Global.n = 2
191 191 self.default_config.Global.reset_config = False
192 192 self.default_config.Global.clean_logs = True
193 193 self.default_config.Global.signal = 2
194 194 self.default_config.Global.daemonize = False
195 195
196 196 def create_command_line_config(self):
197 197 """Create and return a command line config loader."""
198 198 return IPClusterCLLoader(
199 199 description=self.description,
200 200 version=release.version
201 201 )
202 202
203 203 def find_resources(self):
204 204 subcommand = self.command_line_config.Global.subcommand
205 205 if subcommand=='list':
206 206 self.list_cluster_dirs()
207 207 # Exit immediately because there is nothing left to do.
208 208 self.exit()
209 209 elif subcommand=='create':
210 210 self.auto_create_cluster_dir = True
211 211 super(IPClusterApp, self).find_resources()
212 212 elif subcommand=='start' or subcommand=='stop':
213 213 self.auto_create_cluster_dir = False
214 214 try:
215 215 super(IPClusterApp, self).find_resources()
216 216 except ClusterDirError:
217 217 raise ClusterDirError(
218 218 "Could not find a cluster directory. A cluster dir must "
219 219 "be created before running 'ipcluster start'. Do "
220 220 "'ipcluster create -h' or 'ipcluster list -h' for more "
221 221 "information about creating and listing cluster dirs."
222 222 )
223 223
224 224 def list_cluster_dirs(self):
225 225 # Find the search paths
226 226 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
227 227 if cluster_dir_paths:
228 228 cluster_dir_paths = cluster_dir_paths.split(':')
229 229 else:
230 230 cluster_dir_paths = []
231 231 try:
232 232 ipython_dir = self.command_line_config.Global.ipython_dir
233 233 except AttributeError:
234 234 ipython_dir = self.default_config.Global.ipython_dir
235 235 paths = [os.getcwd(), ipython_dir] + \
236 236 cluster_dir_paths
237 237 paths = list(set(paths))
238 238
239 239 self.log.info('Searching for cluster dirs in paths: %r' % paths)
240 240 for path in paths:
241 241 files = os.listdir(path)
242 242 for f in files:
243 243 full_path = os.path.join(path, f)
244 244 if os.path.isdir(full_path) and f.startswith('cluster_'):
245 245 profile = full_path.split('_')[-1]
246 246 start_cmd = '"ipcluster start -n 4 -p %s"' % profile
247 247 print start_cmd + " ==> " + full_path
248 248
249 249 def pre_construct(self):
250 250 # This is where we cd to the working directory.
251 251 super(IPClusterApp, self).pre_construct()
252 252 config = self.master_config
253 253 try:
254 254 daemon = config.Global.daemonize
255 255 if daemon:
256 256 config.Global.log_to_file = True
257 257 except AttributeError:
258 258 pass
259 259
260 260 def construct(self):
261 261 config = self.master_config
262 262 if config.Global.subcommand=='list':
263 263 pass
264 264 elif config.Global.subcommand=='create':
265 265 self.log.info('Copying default config files to cluster directory '
266 266 '[overwrite=%r]' % (config.Global.reset_config,))
267 267 self.cluster_dir_obj.copy_all_config_files(overwrite=config.Global.reset_config)
268 268 elif config.Global.subcommand=='start':
269 269 self.start_logging()
270 270 reactor.callWhenRunning(self.start_launchers)
271 271
272 272 def start_launchers(self):
273 273 config = self.master_config
274 274
275 275 # Create the launchers
276 276 el_class = import_item(config.Global.engine_launcher)
277 277 self.engine_launcher = el_class(
278 278 self.cluster_dir, config=config
279 279 )
280 280 cl_class = import_item(config.Global.controller_launcher)
281 281 self.controller_launcher = cl_class(
282 282 self.cluster_dir, config=config
283 283 )
284 284
285 285 # Setup signals
286 286 signal.signal(signal.SIGINT, self.stop_launchers)
287 287
288 288 # Setup the observing of stopping
289 289 d1 = self.controller_launcher.observe_stop()
290 290 d1.addCallback(self.stop_engines)
291 291 d1.addErrback(self.err_and_stop)
292 292 # If this triggers, just let them die
293 293 # d2 = self.engine_launcher.observe_stop()
294 294
295 295 # Start the controller and engines
296 296 d = self.controller_launcher.start(
297 297 profile=None, cluster_dir=config.Global.cluster_dir
298 298 )
299 299 d.addCallback(lambda _: self.start_engines())
300 300 d.addErrback(self.err_and_stop)
301 301
302 302 def err_and_stop(self, f):
303 303 log.msg('Unexpected error in ipcluster:')
304 304 log.err(f)
305 305 reactor.stop()
306 306
307 307 def stop_engines(self, r):
308 308 return self.engine_launcher.stop()
309 309
310 310 def start_engines(self):
311 311 config = self.master_config
312 312 d = self.engine_launcher.start(
313 313 config.Global.n,
314 314 profile=None, cluster_dir=config.Global.cluster_dir
315 315 )
316 316 return d
317 317
318 318 def stop_launchers(self, signum, frame):
319 319 log.msg("Stopping cluster")
320 320 d1 = self.engine_launcher.stop()
321 321 d2 = self.controller_launcher.stop()
322 322 # d1.addCallback(lambda _: self.controller_launcher.stop)
323 323 d1.addErrback(self.err_and_stop)
324 324 d2.addErrback(self.err_and_stop)
325 325 reactor.callLater(2.0, reactor.stop)
326 326
327 327 def start_logging(self):
328 328 # Remove old log files
329 329 if self.master_config.Global.clean_logs:
330 330 log_dir = self.master_config.Global.log_dir
331 331 for f in os.listdir(log_dir):
332 332 if f.startswith('ipengine' + '-') and f.endswith('.log'):
333 333 os.remove(os.path.join(log_dir, f))
334 334 for f in os.listdir(log_dir):
335 335 if f.startswith('ipcontroller' + '-') and f.endswith('.log'):
336 336 os.remove(os.path.join(log_dir, f))
337 337 super(IPClusterApp, self).start_logging()
338 338
339 339 def start_app(self):
340 340 """Start the application, depending on what subcommand is used."""
341 341 subcmd = self.master_config.Global.subcommand
342 342 if subcmd=='create' or subcmd=='list':
343 343 return
344 344 elif subcmd=='start':
345 345 self.start_app_start()
346 346 elif subcmd=='stop':
347 347 self.start_app_stop()
348 348
349 349 def start_app_start(self):
350 350 """Start the app for the start subcommand."""
351 351 config = self.master_config
352 352 # First see if the cluster is already running
353 353 try:
354 354 pid = self.get_pid_from_file()
355 355 except PIDFileError:
356 356 pass
357 357 else:
358 358 self.log.critical(
359 359 'Cluster is already running with [pid=%s]. '
360 360 'use "ipcluster stop" to stop the cluster.' % pid
361 361 )
362 362 # Here I exit with a unusual exit status that other processes
363 363 # can watch for to learn how I existed.
364 364 self.exit(ALREADY_STARTED)
365 365
366 366 # Now log and daemonize
367 367 self.log.info(
368 368 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
369 369 )
370 370 if config.Global.daemonize:
371 371 if os.name=='posix':
372 372 daemonize()
373 373
374 374 # Now write the new pid file AFTER our new forked pid is active.
375 375 self.write_pid_file()
376 376 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
377 377 reactor.run()
378 378
379 379 def start_app_stop(self):
380 380 """Start the app for the stop subcommand."""
381 381 config = self.master_config
382 382 try:
383 383 pid = self.get_pid_from_file()
384 384 except PIDFileError:
385 385 self.log.critical(
386 386 'Problem reading pid file, cluster is probably not running.'
387 387 )
388 388 # Here I exit with a unusual exit status that other processes
389 389 # can watch for to learn how I existed.
390 390 self.exit(ALREADY_STOPPED)
391 sig = config.Global.signal
392 self.log.info(
393 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
394 )
395 os.kill(pid, sig)
396
391 else:
392 if os.name=='posix':
393 sig = config.Global.signal
394 self.log.info(
395 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
396 )
397 os.kill(pid, sig)
398 elif os.name='nt':
399 # As of right now, we don't support daemonize on Windows, so
400 # stop will not do anything. Minimally, it should clean up the
401 # old .pid files.
402 self.remove_pid_file()
397 403
398 404 def launch_new_instance():
399 405 """Create and run the IPython cluster."""
400 406 app = IPClusterApp()
401 407 app.start()
402 408
403 409
404 410 if __name__ == '__main__':
405 411 launch_new_instance()
406 412
General Comments 0
You need to be logged in to leave comments. Login now