lunch-0.4.0/0000755000000000000000000000000011437735025011331 5ustar rootrootlunch-0.4.0/ChangeLog0000644000000000000000000030174411437735014013112 0ustar rootrootchangeset: 495:6cadd74a04c0 branch: 0.3 tag: tip user: Tristan Matthews date: Tue Aug 17 12:32:33 2010 -0400 summary: Fixed typo in example changeset: 494:8941839f3298 branch: 0.3 user: Tristan Matthews date: Tue Aug 17 12:28:34 2010 -0400 summary: Critical punctuation change in runner.py changeset: 493:045ba69dd8bc branch: 0.3 user: Alexandre Quessy date: Tue Aug 17 12:13:02 2010 -0400 summary: Probably not showing no route to host error bug. changeset: 492:6b5509736d07 branch: 0.3 user: Alexandre Quessy date: Tue Aug 17 10:32:44 2010 -0400 summary: more verbose when killing a running master changeset: 491:0f716d9b1f2b branch: 0.3 user: Alexandre Quessy date: Fri Jul 30 15:41:14 2010 -0400 summary: uncommented todos that are implemented changeset: 490:c1b155d57ff0 branch: 0.3 user: Alexandre Quessy date: Thu Jul 29 15:58:43 2010 -0400 summary: Improved the doc about ssh-agent changeset: 489:6a278a2bb6af branch: 0.3 user: Alexandre Quessy date: Thu Jul 29 09:35:36 2010 -0400 summary: updated to version 0.3.6 - not yet released changeset: 488:a6e029bc3f36 branch: 0.3 user: Alexandre Quessy date: Thu Jul 29 09:31:51 2010 -0400 summary: Added tag 0.3.5 for changeset ce1c9c89f03c changeset: 487:ce1c9c89f03c branch: 0.3 tag: 0.3.5 user: Alexandre Quessy date: Thu Jul 29 09:31:39 2010 -0400 summary: minor change changeset: 486:96e9f73fccb9 branch: 0.3 user: Alexandre Quessy date: Thu Jul 29 09:31:08 2010 -0400 summary: updated RELEASE notes changeset: 485:29fb63e85124 branch: 0.3 user: Alexandre Quessy date: Wed Jul 28 11:34:43 2010 -0400 summary: decreased verbosity in when stopping all /restarting all changeset: 484:8146e788e43c branch: 0.3 user: Alexandre Quessy date: Wed Jul 28 10:29:46 2010 -0400 summary: handle SSH key passphrase prompt changeset: 483:6cf277263cf0 branch: 0.3 user: Alexandre Quessy date: Wed Jul 28 10:23:44 2010 -0400 summary: more info in the GUI changeset: 482:60fc3b22a046 branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 16:12:08 2010 -0400 summary: Guessing the local hostname and LAN address. changeset: 481:c51c1d27954d branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 16:01:56 2010 -0400 summary: updated RELEASE notes changeset: 480:8c9d2a2aa72f branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 16:01:02 2010 -0400 summary: Displaying a command not found error to the user if it happens. changeset: 479:c14884fa2bbe branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 14:21:51 2010 -0400 summary: more error message in case of a SSH error changeset: 478:f916675a526e branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 14:10:28 2010 -0400 summary: more info when we have a ssh error changeset: 477:1b7f49611efa branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 13:53:47 2010 -0400 summary: not disabling commands in case of a SSH error changeset: 476:398784f198ae branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 13:25:32 2010 -0400 summary: fixed a typo changeset: 475:25b80f8f48d9 branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:51:44 2010 -0400 summary: updated RELEASE notes changeset: 474:7561fdaa17c5 branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:48:08 2010 -0400 summary: added a todo changeset: 473:23c415d26e7d branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:42:45 2010 -0400 summary: Added the --kill option to be able to kill already running lunch masters. changeset: 472:282c0c72138f branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:23:43 2010 -0400 summary: Fixed icon file issues changeset: 471:6c9d6f9568b1 branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:01:58 2010 -0400 summary: set the textview to not editable ! changeset: 470:4a05283f404f branch: 0.3 user: Alexandre Quessy date: Wed Jul 21 11:01:15 2010 -0400 summary: Created the kill_lunch_master_if_running function. changeset: 469:d78a40e799c8 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 18:27:55 2010 -0400 summary: updated the RELEASE notes changeset: 468:bd7d339ad202 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 18:22:52 2010 -0400 summary: updated RELEASE notes and added a note in an example changeset: 467:d1cfbdb183b6 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 18:20:26 2010 -0400 summary: added one more SSH error handling changeset: 466:54f1921a7794 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 18:19:00 2010 -0400 summary: Handling most SSH problems as in #47 changeset: 465:253336984a2c branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 17:03:17 2010 -0400 summary: Added the ssh_port option changeset: 464:074b1452f550 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 16:29:09 2010 -0400 summary: Fixed #82 Child pid is not updated the second time a child is started changeset: 463:59fa4917080b branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 16:17:06 2010 -0400 summary: more icons changeset: 462:a92b384a6889 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 16:02:20 2010 -0400 summary: Got rid of the deprecation warning since we modernized the way the menus are created. changeset: 461:6e7503b7796f branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 15:23:51 2010 -0400 summary: added some bash-fu in the examples changeset: 460:118bcc2f5986 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 15:22:26 2010 -0400 summary: improved how the text looks changeset: 459:52a9736f1ab2 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 14:20:34 2010 -0400 summary: Made the details info resizable - and its font monospace. changeset: 458:36a807f4eec3 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 09:53:48 2010 -0400 summary: Changed lunch-slave.Slave.state for child_state. changeset: 457:7625cfbb224f branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 09:28:06 2010 -0400 summary: heavily commented lunch/commands.py changeset: 456:dccf1cf21a25 branch: 0.3 user: Alexandre Quessy date: Tue Jul 20 08:42:58 2010 -0400 summary: updated NEWS with the very first draft - one year ago. changeset: 455:117e7b70bc19 branch: 0.3 user: Alexandre Quessy date: Mon Jul 19 17:17:22 2010 -0400 summary: updated NEWS and RELEASE notes changeset: 454:19dfae6ece1d branch: 0.3 user: Alexandre Quessy date: Mon Jul 19 17:16:40 2010 -0400 summary: updated version to 0.3.5 - not yet released changeset: 453:df742e98b4b4 branch: 0.3 user: Alexandre Quessy date: Mon Jul 19 17:15:52 2010 -0400 summary: Added tag 0.3.4 for changeset a1281a70d27b changeset: 452:a1281a70d27b branch: 0.3 tag: 0.3.4 user: Alexandre Quessy date: Mon Jul 19 17:12:06 2010 -0400 summary: updated RELEASE notes and ChangeLog changeset: 451:c961f9b1fd31 branch: 0.3 user: Alexandre Quessy date: Mon Jul 19 16:49:18 2010 -0400 summary: To kill a child, need to send the signal to the child, not the slave, otherwise, the slave crashes, and the child crashes with it. changeset: 450:24600a9ca278 branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 17:01:52 2010 -0400 summary: better message at shutdown time changeset: 449:6ad119a44459 branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 16:59:34 2010 -0400 summary: added an item in the debug textview changeset: 448:c801ce11353d branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 16:08:10 2010 -0400 summary: More info in the textview changeset: 447:afa7e206d8aa branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 15:53:19 2010 -0400 summary: Critical bug fix changeset: 446:69331c82525b branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 15:46:37 2010 -0400 summary: wrapping text to 7-0 columns in the textview changeset: 445:78c892a37c13 branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 13:39:25 2010 -0400 summary: added a textview to show command details changeset: 444:3e0abb063df7 branch: 0.3 user: Alexandre Quessy date: Fri Jul 16 12:44:03 2010 -0400 summary: Added a non-working trial at setting tooltips for rows in the tree view changeset: 443:080e5e2bfc2f branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 18:09:25 2010 -0400 summary: updated to 0.3.3 - not yet released yet changeset: 442:d74f658d1774 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 18:07:41 2010 -0400 summary: Added tag 0.3.3 for changeset 7fa19f686503 changeset: 441:7fa19f686503 branch: 0.3 tag: 0.3.3 user: Alexandre Quessy date: Thu Jul 15 15:59:44 2010 -0400 summary: disabled a unit test changeset: 440:4cfd4cb85a51 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:37:34 2010 -0400 summary: updated release notes and man pages titles changeset: 439:d23d66f27c93 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:31:47 2010 -0400 summary: Printing big error message if some children are still running when we quit changeset: 438:c35a18f3b777 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:28:01 2010 -0400 summary: updated RELEASE changeset: 437:e4d2d56af73b branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:25:39 2010 -0400 summary: Increasing time before sending SIGKILL changeset: 436:af6c37b17ddb branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:18:49 2010 -0400 summary: updated credits changeset: 435:2a1047357846 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:14:37 2010 -0400 summary: updated RELEASE changeset: 434:ac77b4227aca branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:14:01 2010 -0400 summary: added the enabled keyword changeset: 433:9f1e31f5ab22 branch: 0.3 user: Alexandre Quessy date: Thu Jul 15 15:11:50 2010 -0400 summary: changed title keyword for identifier + Making sure log file is not a dir changeset: 432:ef330eead10d branch: 0.3 user: Alexandre Quessy date: Thu Jul 08 14:31:19 2010 -0400 summary: Fixed the kill dependencies bug! changeset: 431:238be3936412 branch: 0.3 user: Alexandre Quessy date: Wed Jul 07 13:47:13 2010 -0400 summary: Fixed Master.restart_all() changeset: 430:3c1a849e25d9 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 11:42:33 2010 -0400 summary: Added Master.cleanup(), which cleans up the reactor. changeset: 429:b301ca3f6248 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 10:57:58 2010 -0400 summary: Quitting the slave when removing a command. changeset: 428:06b3946564c8 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 10:54:04 2010 -0400 summary: Made the unit tests silent. changeset: 427:9b1c551e3ff8 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 10:50:33 2010 -0400 summary: changed xeyes for man man changeset: 426:fd880ba78632 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 10:47:50 2010 -0400 summary: Command.quit_slave now returns a Deferred. changeset: 425:89126a009119 branch: 0.3 user: Alexandre Quessy date: Tue Jul 06 02:33:01 2010 -0400 summary: added some state checking in Master.remove_command changeset: 424:020e757f2400 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:52:51 2010 -0400 summary: updated version to 0.3.3 - the next to be released later changeset: 423:6fdf3336349c branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:51:25 2010 -0400 summary: Added tag 0.3.2 for changeset 7935d4cf70a8 changeset: 422:7935d4cf70a8 branch: 0.3 tag: 0.3.2 user: Alexandre Quessy date: Wed Jun 23 00:50:30 2010 -0400 summary: updated INSTALL changeset: 421:4f0d85bfa3f7 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:49:42 2010 -0400 summary: Moving README.txt to README. Updated Makefile changeset: 420:a194a9fd9637 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:44:49 2010 -0400 summary: Added INSTALL and COPYING files. Updated README changeset: 419:1e97f6f7a054 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:34:48 2010 -0400 summary: updated ChangeLog changeset: 418:95937fc338f0 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:34:27 2010 -0400 summary: Updated the man pages a lot. Added man_lunch-slave.txt. changeset: 417:f80ba491e3a1 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:27:44 2010 -0400 summary: lunch-slave must have the same version number than the rest. changeset: 416:8e0409943657 branch: 0.3 user: Alexandre Quessy date: Wed Jun 23 00:26:04 2010 -0400 summary: updated NEWS and RELEASE changeset: 415:6b8c4ac209e1 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:58:08 2010 -0400 summary: updated RELEASE changeset: 414:7cef0ebf2888 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:57:46 2010 -0400 summary: Do not ask confirmation to exit when no process is running. changeset: 413:210664f5e152 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:53:50 2010 -0400 summary: updated RELEASE changeset: 412:d5bd85040c64 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:42:52 2010 -0400 summary: Fine tuned how many times to try before giving up changeset: 411:3b0e80da4132 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:17:57 2010 -0400 summary: decrease default try again time changeset: 410:1d6ef218bc73 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 12:15:17 2010 -0400 summary: New feature: double the time to wait before trying again instead of giving up. changeset: 409:4e78da2ed9ce branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 11:16:18 2010 -0400 summary: towards adding try_again_delay changeset: 408:e6f2b7d4f800 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:37:54 2010 -0400 summary: updated version number to 0.3.1 - not yet released changeset: 407:61f244033015 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:35:44 2010 -0400 summary: Added tag 0.3.1 for changeset 3c9f05bf91ce changeset: 406:3c9f05bf91ce branch: 0.3 tag: 0.3.1 user: Alexandre Quessy date: Fri Jun 18 10:35:41 2010 -0400 summary: updated ChangeLog again changeset: 405:7fa935a62372 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:34:52 2010 -0400 summary: updated NEWS and RELEASE again changeset: 404:6479240e3c5c branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:33:45 2010 -0400 summary: updated docstring for the whole package changeset: 403:857298873e12 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:32:20 2010 -0400 summary: Fixed bad version in runner.py changeset: 402:c897c5ac5b51 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:31:19 2010 -0400 summary: minor improvement in the man page changeset: 401:75641ce37a81 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:30:09 2010 -0400 summary: updated NEWS and RELEASE again changeset: 400:c9dd854005c4 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:29:24 2010 -0400 summary: Fixed a crash when trying to log to a file. changeset: 399:02cf0fbf6001 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:21:04 2010 -0400 summary: updated ChangeLog changeset: 398:7c49e6ead91c branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:19:57 2010 -0400 summary: Updated NEWS and RELEASE. Decreased again default verbosity. changeset: 397:9bebbcb58b1f branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:09:03 2010 -0400 summary: Improved logging once again changeset: 396:1b0b66b76367 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 10:00:01 2010 -0400 summary: decreased log level for when we give up changeset: 395:60cbcf5307c6 branch: 0.3 user: Alexandre Quessy date: Fri Jun 18 05:47:44 2010 -0400 summary: removed more prints changeset: 394:8a3185bca03d branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 17:33:54 2010 -0400 summary: Added the show_about_dialog method changeset: 393:679154d2edce branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 17:26:34 2010 -0400 summary: less verbosity in dialogs changeset: 392:03862df29041 branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 16:37:33 2010 -0400 summary: changes in logging verbosity changeset: 391:86f5e57a5172 branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 16:00:14 2010 -0400 summary: Fixed the traceback that was there since we updated the logging. changeset: 390:e0a94fbd707b branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 15:10:55 2010 -0400 summary: More logging for when we start processes changeset: 389:b773b60be23d branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 15:03:44 2010 -0400 summary: Modernized logging. changeset: 388:c2f5c30a9954 branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 13:16:20 2010 -0400 summary: Created the confirm_and_quit method in the gui. changeset: 387:ba9718a1bdb7 branch: 0.3 user: Alexandre Quessy date: Thu Jun 17 13:06:28 2010 -0400 summary: Checking for a lunch master that's a Python, not "lunch" changeset: 386:bd1d454b3c4b branch: 0.3 user: Alexandre Quessy date: Wed Jun 16 10:54:27 2010 -0400 summary: updated version number to 0.3.1 - not yet released changeset: 385:ea10f0c116d4 branch: 0.3 user: Alexandre Quessy date: Wed Jun 16 10:52:31 2010 -0400 summary: Added tag 0.3.0 for changeset 330a4bf7f0ec changeset: 384:330a4bf7f0ec branch: 0.3 tag: 0.3.0 user: Alexandre Quessy date: Wed Jun 16 10:38:56 2010 -0400 summary: updated ChangeLog changeset: 383:b969c8494f83 branch: 0.3 user: Alexandre Quessy date: Wed Jun 16 10:34:57 2010 -0400 summary: added RELEASE, updated NEWS. Simplified version number in setup.py changeset: 382:9706f9391895 branch: 0.3 user: Alexandre Quessy date: Tue Jun 15 22:20:44 2010 -0400 summary: added the NEWS file changeset: 381:fc06fbe72309 branch: 0.3 user: Alexandre Quessy date: Tue Jun 15 22:02:19 2010 -0400 summary: Fixed a comment. changeset: 380:76468dea5154 branch: 0.3 user: Alexandre Quessy date: Tue Jun 15 15:10:32 2010 -0400 summary: Changing buttons sensitivity according to slaves state. changeset: 379:9fe2bd4268b8 branch: 0.3 user: Alexandre Quessy date: Tue Jun 15 14:40:35 2010 -0400 summary: Flushing child log file every 0.1 s changeset: 378:e887d1eb04a4 branch: 0.3 user: Alexandre Quessy date: Tue Jun 15 14:13:35 2010 -0400 summary: added a manual menu item :) changeset: 377:bd93c2456d98 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 16:58:58 2010 -0400 summary: Added GAVEUP info state for when the child died too quickly. changeset: 376:35f62e64fa44 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 16:17:48 2010 -0400 summary: added state TODO changeset: 375:e40e08eebb54 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 16:14:17 2010 -0400 summary: added a FAILED status and a DONE one. changeset: 374:7fc4c58bc6c1 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 16:01:41 2010 -0400 summary: Added a button to start and stop child processes changeset: 373:a4b9ad8694d9 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 15:42:23 2010 -0400 summary: improved an example changeset: 372:645185553dd8 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 15:39:24 2010 -0400 summary: Fixed the removal of commands in the GUI. changeset: 371:4f40d9474eff branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 15:18:37 2010 -0400 summary: updated simple.py example + added signal handlers in GUI for add/rm command changeset: 370:6a8f29bac6bc branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 15:12:09 2010 -0400 summary: reordered methods changeset: 369:8e4b527d1c81 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 14:51:21 2010 -0400 summary: got rid of the singleton in lunch master changeset: 368:dc0068a17a58 branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 14:27:58 2010 -0400 summary: examples use the GUI too changeset: 367:8255da97e11d branch: 0.3 user: Alexandre Quessy date: Mon Jun 14 13:54:18 2010 -0400 summary: added a counter in the add_remove example changeset: 366:82dbab8e53fa branch: 0.3 user: Alexandre Quessy date: Fri Jun 11 13:15:20 2010 -0400 summary: Added the retval message (not yet stored on the slave's side) changeset: 365:b1abe2d2c6b9 branch: 0.3 user: Alexandre Quessy date: Fri Jun 11 12:27:52 2010 -0400 summary: adding comment to prepare for not giving up when cannot launch a process changeset: 364:7fabd2c1180f branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 16:00:17 2010 -0400 summary: fixed a critical error changeset: 363:268cfd61484a branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 15:53:20 2010 -0400 summary: Erasing the PID file at shutdown time. changeset: 362:d4dff1e678df branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 15:38:30 2010 -0400 summary: Added execute_config_file and start_logging functions in master.py changeset: 361:c1a6992c1559 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 15:27:48 2010 -0400 summary: Able to add and remove commands using lunch library changeset: 360:8548fce202da branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 14:57:59 2010 -0400 summary: The lunch master library example works changeset: 359:8426e84e33eb branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 14:38:51 2010 -0400 summary: The example using lunch lib works! changeset: 358:296706afab0a branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 13:34:37 2010 -0400 summary: Fixed method signatures. changeset: 357:78f059597aea branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 13:18:28 2010 -0400 summary: Fixed a critical mistake in master.py + removed MasterError. changeset: 356:1f7f146e894d branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 13:08:04 2010 -0400 summary: Fixed errors found by pychecker in master.py changeset: 355:8c7443d5b96d branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:35:08 2010 -0400 summary: added a simple example that is not working yet changeset: 354:522a5316ad6b branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:34:33 2010 -0400 summary: added a todo note changeset: 353:f93781587006 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:31:08 2010 -0400 summary: moved the constants to states.py changeset: 352:880e6cb2a975 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:25:26 2010 -0400 summary: removed executable bit from more files. changeset: 351:d6a0e1711659 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:24:55 2010 -0400 summary: Importing __version__ from __init__ in gui.py changeset: 350:89e448fa9f0e branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:23:24 2010 -0400 summary: removed __main__ from gui.py changeset: 349:97a10bc1651b branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 10:20:57 2010 -0400 summary: splitted the master.py by creating commands.py changeset: 348:442fc0e6eeab branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 09:57:19 2010 -0400 summary: Improved how help messages are displayed in lunch-slave changeset: 347:2f585b1a5836 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 09:40:19 2010 -0400 summary: Fixed the help command in lunch-slave changeset: 346:9b6913cf6d7f branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 09:35:41 2010 -0400 summary: added utils/build-doc.sh, which builds the HTML doc changeset: 345:f571511c0bc3 branch: 0.3 user: Alexandre Quessy date: Thu Jun 10 09:27:24 2010 -0400 summary: changed distutils to setuptools in setup.py changeset: 344:de18bb23c8c6 branch: 0.3 user: Alexandre Quessy date: Wed Jun 09 17:33:53 2010 -0400 summary: Updating version number to 0.3.0 - but it's not yet released. changeset: 343:b32b1dd1281d branch: 0.2 user: Alexandre Quessy date: Tue Jun 01 09:37:00 2010 -0400 summary: Added tag 0.2.22 for changeset a899bcd4a269 changeset: 342:a899bcd4a269 branch: 0.2 tag: 0.2.22 user: Alexandre Quessy date: Tue Jun 01 09:36:42 2010 -0400 summary: updating ChangeLog for release 0.2.22 changeset: 341:8daedcf095c5 branch: 0.2 user: Alexandre Quessy date: Tue Jun 01 09:14:45 2010 -0400 summary: removed man pages from the repository. Added a HISTORY section in man page. changeset: 340:2d6b1616aefa branch: 0.2 user: Alexandre Quessy date: Tue Jun 01 09:01:32 2010 -0400 summary: updated version to 0.2.22 changeset: 339:990f30ac58cf branch: 0.2 user: Alexandre Quessy date: Tue Jun 01 08:32:39 2010 -0400 summary: Specifying the encoding in the .desktop file is deprecated changeset: 338:14ff3d32d5ee branch: 0.2 parent: 321:67521f514596 user: Alexandre Quessy date: Tue Jun 01 08:31:24 2010 -0400 summary: adding a very detailled ChangeLog changeset: 337:13d9a264fb31 branch: packaging user: Alexandre Quessy date: Sun May 30 17:38:38 2010 -0400 summary: closing this branch. The debian files are now in svn://svn.debian.org/python-apps/packages/lunch/trunk/ changeset: 336:01b5fdd4df33 branch: packaging user: Alexandre Quessy date: Sun May 30 17:29:55 2010 -0400 summary: removed everything in the packaging branch. It's not in http://svn.debian.org/viewsvn/python-apps/packages/lunch/trunk/ changeset: 335:82f797ee1f98 branch: packaging user: Alexandre Quessy date: Sun May 30 16:54:18 2010 -0400 summary: depend on python, not python-all changeset: 334:299dba47c93a branch: packaging user: Alexandre Quessy date: Sun May 30 16:50:51 2010 -0400 summary: added python-all to deps changeset: 333:05e7ed1be637 branch: packaging user: Alexandre Quessy date: Sun May 30 13:21:33 2010 -0400 summary: updated the date in changelog changeset: 332:3dfe6d5d55c4 branch: packaging user: Alexandre Quessy date: Sun May 30 13:18:35 2010 -0400 summary: got rid of the unused substitution variable ${python:Versions} changeset: 331:0b482087a2b0 branch: packaging user: Alexandre Quessy date: Sun May 30 13:06:18 2010 -0400 summary: Should have fixed many errors and warnings: changeset: 330:531cc942b7e9 branch: packaging user: Alexandre Quessy date: Sat May 29 21:03:14 2010 -0400 summary: adding watch file. re-added cdbs as build-dep. Build-Depending on python-all changeset: 329:3f9fcafec80a branch: packaging user: Alexandre Quessy date: Sat May 29 20:45:28 2010 -0400 summary: fixed build-depends changeset: 328:60f16bfd5d9b branch: packaging user: Alexandre Quessy date: Sat May 29 18:38:07 2010 -0400 summary: progress... but it's not working. changeset: 327:1813d53aff5e branch: packaging user: Alexandre Quessy date: Sat May 29 18:16:39 2010 -0400 summary: Updated copyright. Remove python-central from the rules. changeset: 326:7a61363b8d72 branch: packaging user: Alexandre Quessy date: Fri May 28 13:41:39 2010 -0400 summary: renamed changelog-sid for changelog changeset: 325:1c5674fc8994 branch: packaging user: Alexandre Quessy date: Fri May 28 04:50:11 2010 -0400 summary: fixed a mistake in ownership changeset: 324:649413dddafe branch: packaging user: Alexandre Quessy date: Fri May 28 04:47:47 2010 -0400 summary: changed maintainer for the Python App Package Team changeset: 323:a3e28bf70f8f branch: packaging user: Alexandre Quessy date: Fri May 28 03:27:23 2010 -0400 summary: now it's the correct message changeset: 322:95e1c964b45c branch: packaging parent: 320:03f4178d2ba1 user: Alexandre Quessy date: Thu May 27 21:02:09 2010 -0400 summary: a changelog for sid changeset: 321:67521f514596 branch: 0.2 parent: 318:717965e05673 user: Alexandre Quessy date: Thu May 27 20:58:34 2010 -0400 summary: Added tag 0.2.21 for changeset 717965e05673 changeset: 320:03f4178d2ba1 branch: packaging user: Alexandre Quessy date: Thu May 27 20:51:22 2010 -0400 summary: trying to fix some lintian warnings changeset: 319:20cd6f66639e branch: packaging parent: 315:28dfbe392e02 user: Alexandre Quessy date: Thu May 27 20:42:24 2010 -0400 summary: changed for the utils debian section changeset: 318:717965e05673 branch: 0.2 tag: 0.2.21 user: Alexandre Quessy date: Thu May 27 20:37:40 2010 -0400 summary: updated version to 0.2.21 changeset: 317:2ecc9c01f731 branch: 0.2 user: Alexandre Quessy date: Thu May 27 20:33:00 2010 -0400 summary: removed .png from ignored files, since it's versionned changeset: 316:269a34008a73 branch: 0.2 parent: 313:e0c53d3f90e0 user: Alexandre Quessy date: Thu May 27 20:31:04 2010 -0400 summary: removed debian dir from "trunk" changeset: 315:28dfbe392e02 branch: packaging user: Alexandre Quessy date: Thu May 27 20:30:35 2010 -0400 summary: improved description. Removed imagemagick from build-deps. Put openssh-server in recommended packages. changeset: 314:8270707903e6 branch: packaging user: Alexandre Quessy date: Thu May 27 20:18:35 2010 -0400 summary: removed all files unrelated to packaging in the new "packaging" branch. changeset: 313:e0c53d3f90e0 branch: 0.2 user: Alexandre Quessy date: Mon Apr 12 04:06:35 2010 -0400 summary: updated man pages when building. changeset: 312:5876cff998c7 branch: 0.2 user: Alexandre Quessy date: Mon Apr 12 04:06:19 2010 -0400 summary: Added tag 0.2.20 for changeset 118bd854acff changeset: 311:118bd854acff branch: 0.2 tag: 0.2.20 user: Alexandre Quessy date: Mon Apr 12 04:03:11 2010 -0400 summary: updated version to 0.2.20 changeset: 310:3a9febbc4c79 branch: 0.2 user: Alexandre Quessy date: Mon Apr 12 04:01:06 2010 -0400 summary: When a process with the PID of the master is found, we check if it has the string "lunch" in its command. changeset: 309:fbee5bbfe582 branch: 0.2 user: Alexandre Quessy date: Mon Apr 12 03:28:05 2010 -0400 summary: removed the command to convert the svg to png in setup.py changeset: 308:eb4894082b53 branch: 0.2 user: Alexandre Quessy date: Wed Feb 17 22:51:46 2010 -0500 summary: Made a very nice 48x48 icon with inkscape changeset: 307:ea94e780a2f6 branch: 0.2 user: Alexandre Quessy date: Fri Feb 05 08:59:17 2010 -0500 summary: Added tag 0.2.19 for changeset 1396694f1a3c changeset: 306:1396694f1a3c branch: 0.2 tag: 0.2.19 user: Alexandre Quessy date: Fri Feb 05 08:56:44 2010 -0500 summary: ready for packaging verion 0.2.19 changeset: 305:56d11b3ffabc branch: 0.2 user: Alexandre Quessy date: Fri Feb 05 01:20:56 2010 -0500 summary: Added tag 0.2.18 for changeset 139a981064c4 changeset: 304:139a981064c4 branch: 0.2 tag: 0.2.18 user: Alexandre Quessy date: Fri Feb 05 01:19:46 2010 -0500 summary: updated version to 0.2.18. Adding icons and man page in repository. changeset: 303:45f60e3c5363 branch: 0.2 user: Alexandre Quessy date: Tue Feb 02 17:00:00 2010 -0500 summary: fixed bug with dialogs when an error occurs in the lunch file. changeset: 302:ff1cb5d02f43 branch: 0.2 user: Alexandre Quessy date: Tue Feb 02 11:19:53 2010 -0500 summary: Slitting dialogs from gui.py changeset: 301:54065499d345 branch: 0.2 user: Alexandre Quessy date: Wed Jan 27 21:20:17 2010 -0500 summary: added process tree in man page. Cleaned it up a bit. changeset: 300:0fe7324c6d4a branch: 0.2 user: Alexandre Quessy date: Wed Jan 27 21:14:35 2010 -0500 summary: improved man page again a little changeset: 299:9569c1f47f8d branch: 0.2 user: Alexandre Quessy date: Wed Jan 27 21:13:06 2010 -0500 summary: improved man page a lot changeset: 298:c5fa9360998b branch: 0.2 user: Alexandre Quessy date: Mon Jan 25 11:32:09 2010 -0500 summary: version 0.2.17 changeset: 297:4aa9790fa58b branch: 0.2 parent: 296:73e39f42d2fe parent: 295:fed11e6ebd08 user: Alexandre Quessy date: Mon Jan 25 10:44:07 2010 -0500 summary: merged changeset: 296:73e39f42d2fe branch: 0.2 parent: 294:27aa95291e12 user: Alexandre Quessy date: Mon Jan 25 10:43:43 2010 -0500 summary: trivial change. changeset: 295:fed11e6ebd08 branch: 0.2 parent: 292:7b8155afb8d8 user: Alexandre Quessy date: Mon Jan 25 10:41:45 2010 -0500 summary: Added tag 0.2.16 for changeset 7b8155afb8d8 changeset: 294:27aa95291e12 branch: 0.2 user: Alexandre Quessy date: Mon Jan 25 10:40:59 2010 -0500 summary: Made the confirm close dialog modal. (blocks the rest of the app) changeset: 293:60b9693f765e branch: 0.2 parent: 292:7b8155afb8d8 parent: 285:a6f5aaccd37f user: Alexandre Quessy date: Mon Jan 25 10:36:28 2010 -0500 summary: merged. changeset: 292:7b8155afb8d8 branch: 0.2 tag: 0.2.16 user: Alexandre Quessy date: Fri Jan 22 14:11:34 2010 -0500 summary: Version 0.2.16: Fixed remove child log file. changeset: 291:af5a14cc7c66 branch: 0.2 user: Alexandre Quessy date: Fri Jan 22 14:10:11 2010 -0500 summary: Fixed remote log file not being tailed in gui changeset: 290:e499d5d3e925 branch: 0.2 user: Alexandre Quessy date: Fri Jan 22 14:01:01 2010 -0500 summary: updated version again to 0.2.15 changeset: 289:f67365e47c7c branch: 0.2 user: Alexandre Quessy date: Fri Jan 22 13:59:42 2010 -0500 summary: added -q option to desktop file. changeset: 288:c23a06c60e79 branch: 0.2 user: Alexandre Quessy date: Fri Jan 22 13:57:13 2010 -0500 summary: Version number 0.2.14. Added menu item to view master log file. changeset: 287:1515d8da8138 branch: 0.2 user: Alexandre Quessy date: Fri Jan 22 13:42:20 2010 -0500 summary: Fixed problem with command being respawn even if lifetime smaller than minimum. changeset: 286:67f235d825b0 branch: 0.2 parent: 283:6d31e4b7c9f0 user: Alexandre Quessy date: Fri Jan 22 12:47:45 2010 -0500 summary: Trying to fix the bug with not-found commands being respawned. changeset: 285:a6f5aaccd37f branch: 0.2 user: Alexandre Quessy date: Mon Jan 25 02:24:37 2010 -0500 summary: adding icon for window. changeset: 284:01fb0f7a374d branch: 0.2 user: Alexandre Quessy date: Mon Jan 25 00:26:07 2010 -0500 summary: Added a confirm close dialog. changeset: 283:6d31e4b7c9f0 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:54:09 2010 -0500 summary: removed -g option in examples. changeset: 282:e07cca0b12cc branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:17:10 2010 -0500 summary: Added tag 0.2.13 for changeset c6e2e39b2e74 changeset: 281:c6e2e39b2e74 branch: 0.2 tag: 0.2.13 user: Alexandre Quessy date: Thu Jan 21 16:14:05 2010 -0500 summary: Updated version to 0.2.13 changeset: 280:daec23442eea branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:09:55 2010 -0500 summary: updated man page a bit. changeset: 279:56c5a0835a31 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:04:51 2010 -0500 summary: set minimum lifetime to respawn back to 0.5 seconds. changeset: 278:fadc5135fe93 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:01:57 2010 -0500 summary: Renamed all examples. changeset: 277:0495c88c8e69 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 16:00:17 2010 -0500 summary: modified comments in examples. Installing examples in debian package. changeset: 276:9bc097acbfa8 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 15:50:28 2010 -0500 summary: removed tree view prototype changeset: 275:57dc59c45511 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 15:49:52 2010 -0500 summary: added a view log button that works. changeset: 274:12bc280dc34b branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 14:37:43 2010 -0500 summary: GUI looks a lot better. Changed table of labels for a TreeView. changeset: 273:48c000efb541 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 13:56:31 2010 -0500 summary: able to modify cell in the treeview in prototype. changeset: 272:666da56e543f branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 12:56:14 2010 -0500 summary: adding dummy rows in tree proto changeset: 271:713045fb5fce branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 11:31:32 2010 -0500 summary: making the slave less verbose when a child dies. changeset: 270:4f5ac58a4ef2 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 11:23:45 2010 -0500 summary: Using an iterator instead of copy-pasting the pseudo-tree sorting algorithm. changeset: 269:062d9693ad09 branch: 0.2 parent: 268:698e5cf78445 parent: 267:2c1801a318ec user: Alexandre Quessy date: Thu Jan 21 04:15:36 2010 -0500 summary: getting rid of a useless head in hg history changeset: 268:698e5cf78445 branch: 0.2 parent: 266:e1a7ab99e442 user: Alexandre Quessy date: Thu Jan 21 04:12:18 2010 -0500 summary: Improved man page a little changeset: 267:2c1801a318ec branch: 0.2 parent: 261:a2d1b13ce2d1 user: Alexandre Quessy date: Thu Jan 21 04:09:56 2010 -0500 summary: Added tag 0.2.12 for changeset a2d1b13ce2d1 changeset: 266:e1a7ab99e442 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 03:35:31 2010 -0500 summary: Added GTK error dialog if an error occurs at startup. changeset: 265:97a2694669b8 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 03:12:16 2010 -0500 summary: added error dialog changeset: 264:4df6d74e186c branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 03:07:03 2010 -0500 summary: added tail_log_file function changeset: 263:13032b7f09d7 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 02:43:03 2010 -0500 summary: Fixed a bad bug with GUI: some childs were not killed when quitting by closing the window. changeset: 262:e9995afaeea3 branch: 0.2 user: Alexandre Quessy date: Thu Jan 21 02:38:21 2010 -0500 summary: adding a nice dependency example changeset: 261:a2d1b13ce2d1 branch: 0.2 tag: 0.2.12 user: Alexandre Quessy date: Wed Jan 20 20:56:32 2010 -0500 summary: updated version to 0.2.12 changeset: 260:a3a4e1fecef3 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 20:53:45 2010 -0500 summary: Not respawning those who are supposed to be ran only once. changeset: 259:b10836388009 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 18:11:03 2010 -0500 summary: added example with no respawn. changeset: 258:e76338d038c7 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 17:41:23 2010 -0500 summary: Added tag 0.2.11 for changeset e18146358b2b changeset: 257:e18146358b2b branch: 0.2 tag: 0.2.11 user: Alexandre Quessy date: Wed Jan 20 17:39:43 2010 -0500 summary: Updated version number to 0.2.11 changeset: 256:d41c7284c204 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 17:32:57 2010 -0500 summary: Decreased verbosity. changeset: 255:fc3cce7d432a branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 17:23:37 2010 -0500 summary: Fixed the sleep_after with the new depends option. changeset: 254:47914adfcda9 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 15:44:28 2010 -0500 summary: heu... progress in tree proto. changeset: 253:cbfce5d316d4 branch: 0.2 user: Alexandre Quessy date: Wed Jan 20 12:22:47 2010 -0500 summary: Displaying strings and such in the tree view. changeset: 252:69a914abf467 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 17:42:54 2010 -0500 summary: adding newtree.py proto changeset: 251:c2516eabf7d0 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 16:41:14 2010 -0500 summary: Dependendant childs are killed when their dependency dies. changeset: 250:51db0f4d7413 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 16:23:06 2010 -0500 summary: something is working with the tree sorting, but there is no delay between each launching, and the dependencies do not restart their dependees. changeset: 249:4f814a09de42 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 16:15:32 2010 -0500 summary: graph test works for get_all_dependencies changeset: 248:5e44bb8bdca6 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 15:43:36 2010 -0500 summary: Tree traversal without recursion test works. changeset: 247:0fb49f918f11 branch: 0.2 user: Alexandre Quessy date: Tue Jan 19 14:49:52 2010 -0500 summary: Close to some dependencies managing that works. changeset: 246:8d6ce06160c1 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 16:50:11 2010 -0500 summary: Gradually implementing dependency tree and a looping call to check ps state. changeset: 245:b48f1162a959 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 16:11:54 2010 -0500 summary: Deprecated groups. changeset: 244:7404c7d850c9 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 15:18:08 2010 -0500 summary: added comments and attributes to the master. changeset: 243:e1068666a252 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 15:05:42 2010 -0500 summary: Added run_and_wait in Master, which runs a command and triggers the deferred when done. Not used yet. changeset: 242:85b3ffcb7fcf branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 14:49:32 2010 -0500 summary: Separated the run_once() and open_path() functions in GUI. changeset: 241:911fe0774045 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 14:42:36 2010 -0500 summary: Checking dependency circularity. changeset: 240:a08b6cea06b5 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 14:06:24 2010 -0500 summary: Draft of depends_on function. changeset: 239:0b80a260ad69 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 13:49:25 2010 -0500 summary: Making sure reactor is running prior to stop it. changeset: 238:c823fa10462c branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 13:32:15 2010 -0500 summary: Added tag 0.2.10 for changeset 212bb875b3ee changeset: 237:212bb875b3ee branch: 0.2 tag: 0.2.10 user: Alexandre Quessy date: Mon Jan 18 13:06:12 2010 -0500 summary: Fixed rules changeset: 236:07fd036630a8 branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 13:00:09 2010 -0500 summary: Updating version to 0.2.10. Improved man page a bit. changeset: 235:ab9fb1fe9d4b branch: 0.2 user: Alexandre Quessy date: Mon Jan 18 12:54:11 2010 -0500 summary: Removed glib module dependency. It wasn't working on Hardy. changeset: 234:2bd00480d942 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 22:46:48 2010 -0500 summary: Moved copyright file to the right location. changeset: 233:4d84c1c9c80e branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 22:07:07 2010 -0500 summary: Added tag 0.2.9 for changeset e3527794078c changeset: 232:e3527794078c branch: 0.2 tag: 0.2.9 user: Alexandre Quessy date: Sun Jan 17 22:01:19 2010 -0500 summary: version 0.2.9 : trying to fix ubuntu packaging issues changeset: 231:fbfce6e92fd5 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 17:10:13 2010 -0500 summary: Fixed date for last entry in changelog... changeset: 230:64809cad1897 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 17:05:12 2010 -0500 summary: Fixed the distro name. changeset: 229:5b93ac5c2bcb branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 10:42:22 2010 -0500 summary: Using getProcessValue instead of getProcessOutput changeset: 228:f065cc6ca016 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 00:43:49 2010 -0500 summary: Added tag 0.2.8 for changeset 2edce4f0b438 changeset: 227:2edce4f0b438 branch: 0.2 tag: 0.2.8 user: Alexandre Quessy date: Sun Jan 17 00:40:56 2010 -0500 summary: Updated changelog with version 0.2.8 changeset: 226:5e3124d65013 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 00:35:48 2010 -0500 summary: Added how many times each child ran in GUI. changeset: 225:945deb2be237 branch: 0.2 user: Alexandre Quessy date: Sun Jan 17 00:10:18 2010 -0500 summary: Added horizontal spacers. Looks ok by now. changeset: 224:70d9d4f88437 branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 23:40:20 2010 -0500 summary: implemented the "Open log" menu item. changeset: 223:27e628f9c889 branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 23:19:13 2010 -0500 summary: Catching error when logo not found in about dialog. changeset: 222:e514c46128d0 branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 23:14:58 2010 -0500 summary: The Menu looks better if not in scrollable area. changeset: 221:3c17c1930e75 branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 23:05:28 2010 -0500 summary: Added menu bar and about dialog. changeset: 220:aa35236c255d branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 20:25:59 2010 -0500 summary: Gui looks better with a table. changeset: 219:0e27132bc55b branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 17:52:37 2010 -0500 summary: 0.2.7 : Ubuntu packaging and added command in GUI. changeset: 218:7d907b8da46b branch: 0.2 user: Alexandre Quessy date: Sat Jan 16 12:53:16 2010 -0500 summary: Ubuntu package name. changeset: 217:22bdd2132ccd branch: 0.2 parent: 216:256c95a5d15a parent: 215:8d4726dd531b user: Alexandre Quessy date: Sat Jan 16 02:42:15 2010 -0500 summary: merging with main head changeset: 216:256c95a5d15a branch: 0.2 parent: 212:44621ec2339a user: Alexandre Quessy date: Sat Jan 16 02:41:17 2010 -0500 summary: adding the newgui prototype. changeset: 215:8d4726dd531b branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 19:17:49 2010 -0500 summary: More doc in graph.py. More unit tests too. changeset: 214:23a63a0c8885 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 18:57:38 2010 -0500 summary: Changing permissions on log and config files. changeset: 213:418073a45f71 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 18:15:27 2010 -0500 summary: Added tag 0.2.6 for changeset 44621ec2339a changeset: 212:44621ec2339a branch: 0.2 tag: 0.2.6 user: Alexandre Quessy date: Fri Jan 15 18:14:07 2010 -0500 summary: Updated version number to 0.2.6 changeset: 211:b0148ec77792 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 18:09:19 2010 -0500 summary: Added a clear() to the graph. changeset: 210:3134906bbeeb branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 18:00:25 2010 -0500 summary: Adding test_graph. Changed the graph to a list of list of list instead of dict. changeset: 209:1e74f4cced45 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 16:45:17 2010 -0500 summary: adding graph.py changeset: 208:5ef6fbd14905 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 13:46:29 2010 -0500 summary: Added tag 0.2.5 for changeset 51f79f6fefbc changeset: 207:51f79f6fefbc branch: 0.2 tag: 0.2.5 user: Alexandre Quessy date: Fri Jan 15 13:44:59 2010 -0500 summary: Updating version number to 0.2.5. changeset: 206:8d2785dd5bb2 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 13:40:25 2010 -0500 summary: Child state seem to work, even in GUI. changeset: 205:49dfb8974da2 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 13:14:22 2010 -0500 summary: Oops. Fixed an error with no -g opt. changeset: 204:520f2ce4e033 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 13:13:09 2010 -0500 summary: Added --graphical option to lunch. changeset: 203:c3849a241d55 branch: 0.2 user: Alexandre Quessy date: Fri Jan 15 09:56:24 2010 -0500 summary: updated state diagram changeset: 202:0eecf6e017ad branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 17:33:37 2010 -0500 summary: progress in gui changeset: 201:86388196031e branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 16:58:35 2010 -0500 summary: updated doc generator in Makefile changeset: 200:fbd2431a15fa branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 15:56:49 2010 -0500 summary: Making sure that there is not an other lunch master running with the same lunch config file. changeset: 199:234c41c38276 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 15:33:44 2010 -0500 summary: Added tag 0.2.4 for changeset af93ffb6802b changeset: 198:af93ffb6802b branch: 0.2 tag: 0.2.4 user: Alexandre Quessy date: Thu Jan 14 15:27:03 2010 -0500 summary: updated changelog with bugfix in slave logging changeset: 197:0acb300f0b91 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 15:18:44 2010 -0500 summary: Fixed a very bad mistake since I've inspected visually the code. changeset: 196:4b1a83815383 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 15:16:56 2010 -0500 summary: Creating logdir in slave if it does not exist. changeset: 195:3c0703c221cb branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 15:07:31 2010 -0500 summary: Added tag 0.2.3 for changeset 2927d6ee0c76 changeset: 194:2927d6ee0c76 branch: 0.2 tag: 0.2.3 user: Alexandre Quessy date: Thu Jan 14 14:52:25 2010 -0500 summary: added a little line... changeset: 193:9267c18cba0c branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 14:42:31 2010 -0500 summary: Improved doc again. changeset: 192:d937ec9723fe branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 14:39:30 2010 -0500 summary: added --logging-directory and --log-to-file options to master. changeset: 191:d2ecd1691a70 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 13:57:37 2010 -0500 summary: Lunch master PID is written to /var/tmp/lunch/master-*.pid changeset: 190:24beb061bd77 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 13:53:23 2010 -0500 summary: Able to specify log_dir using the log_dir kwargs of add_command. changeset: 189:9be1212dcf90 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 12:28:39 2010 -0500 summary: add_local_address can support a str or a list of str as arg. changeset: 188:1479432e47c2 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 12:25:02 2010 -0500 summary: updated version number and project description changeset: 187:cf181c1a4887 branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 12:24:24 2010 -0500 summary: updated doc and version number changeset: 186:2b71e08d441c branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 12:16:32 2010 -0500 summary: fixed a debug message changeset: 185:105e38e5265f branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 12:14:48 2010 -0500 summary: made the master a bit less verbose changeset: 184:24c4d93e823a branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 11:55:24 2010 -0500 summary: added add_local_address(addr) and clear_local_addresses() changeset: 183:23ad391c178d branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 11:44:57 2010 -0500 summary: Updated description changeset: 182:d19a1cc5a7de branch: 0.2 user: Alexandre Quessy date: Thu Jan 14 11:43:50 2010 -0500 summary: Fixed a few errors that lintian told us about. changeset: 181:05498cde47a4 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 17:05:13 2010 -0500 summary: (Re-?)Added lunch-slave man page to the debian package. changeset: 180:ae0746784fdb branch: 0.2 parent: 177:67eb5a282463 user: Alexandre Quessy date: Wed Jan 13 16:29:56 2010 -0500 summary: updated man page a bit changeset: 179:9ab3332eb020 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 16:26:56 2010 -0500 summary: Added tag 0.1.8 for changeset 48e89e673179 changeset: 178:48e89e673179 branch: 0.1 tag: 0.1.8 parent: 160:285a116b45d9 user: Alexandre Quessy date: Wed Jan 13 16:25:37 2010 -0500 summary: Update version number. Added to changelog changeset: 177:67eb5a282463 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 16:17:27 2010 -0500 summary: Added tag 0.2.2 for changeset cc801c512ee8 changeset: 176:cc801c512ee8 branch: 0.2 tag: 0.2.2 user: Alexandre Quessy date: Wed Jan 13 16:15:58 2010 -0500 summary: getting rid of the dot in pid file name. changeset: 175:816b984fcf3a branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 16:12:52 2010 -0500 summary: Fixed minor errors in makefile. changeset: 174:c2c55772cca7 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 16:11:10 2010 -0500 summary: updated changelog changeset: 173:89620df143ea branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 16:07:49 2010 -0500 summary: Implemented a minimum life time to childs. changeset: 172:2fd08d329bbc branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:44:41 2010 -0500 summary: got rid of scripts/lunch-0.1 (again?) changeset: 171:c35e30515ae4 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:43:13 2010 -0500 summary: added copyright to debian rules changeset: 170:c06f13f56a25 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:42:19 2010 -0500 summary: added copyright, like in branch 0.1 changeset: 169:ea190ea952b2 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:40:25 2010 -0500 summary: fixed kill -9 in slave. changeset: 168:6b8d0f306b5d branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:08:46 2010 -0500 summary: cleanup in examples changeset: 167:d4959c376931 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:05:07 2010 -0500 summary: updated version in more files. changeset: 166:a43f26aec5e3 branch: 0.2 user: Alexandre Quessy date: Wed Jan 13 15:03:57 2010 -0500 summary: updated version number to 2.2 in files. changeset: 165:43097e7b1e8e branch: py parent: 164:2be953836bc5 parent: 147:45ee2527bdee user: Alexandre Quessy date: Wed Jan 13 14:58:09 2010 -0500 summary: Merging an other branch I resurrected by mistake. changeset: 164:2be953836bc5 branch: py parent: 163:f80879960141 parent: 161:5550c6e7895a user: Alexandre Quessy date: Wed Jan 13 14:52:09 2010 -0500 summary: Merging two branches spawned by error. changeset: 163:f80879960141 branch: py user: Alexandre Quessy date: Wed Jan 13 14:48:06 2010 -0500 summary: fixed errors again in master changeset: 162:19571f15a542 branch: py parent: 133:1fe89ed9da01 user: Alexandre Quessy date: Wed Jan 13 14:43:31 2010 -0500 summary: writing pid file in master, like in branch 0.1 changeset: 161:5550c6e7895a branch: py parent: 133:1fe89ed9da01 user: Alexandre Quessy date: Wed Jan 13 14:39:35 2010 -0500 summary: Passing the whole env from master to slaves. (needed by ssh-agent) changeset: 160:285a116b45d9 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 14:25:38 2010 -0500 summary: adding a whole bunch of kills wo try to make the slave die when killing programatically the master changeset: 159:416682d492a0 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 13:52:09 2010 -0500 summary: updated doc a bit. changeset: 158:c683909a8b44 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 12:05:01 2010 -0500 summary: updating doc to help killing the master. changeset: 157:50c1f335c572 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 11:48:21 2010 -0500 summary: Fixed an other mistake in writing pidfile. changeset: 156:22ce359e2a33 branch: 0.1 user: Alexandre Quessy date: Wed Jan 13 11:45:25 2010 -0500 summary: Fixed problem with pid file changeset: 155:ceeda7aa1b80 branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 16:00:02 2010 -0500 summary: Fixing packaging error according to lintian. changeset: 154:24eb148acb33 branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 15:45:48 2010 -0500 summary: Updating version number to 0.1.7 to check if build is ok. changeset: 153:8158986f95fb branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 15:44:00 2010 -0500 summary: Fixed error with name of executable. changeset: 152:9b0419754f81 branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 14:55:41 2010 -0500 summary: Added tag 0.1.6 for changeset 7172681bcbb6 changeset: 151:7172681bcbb6 branch: 0.1 tag: 0.1.6 user: Alexandre Quessy date: Tue Jan 12 14:52:32 2010 -0500 summary: updated debian package infos. changeset: 150:ac48cd216dbe branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 14:42:48 2010 -0500 summary: Moving examples to ./examples. changeset: 149:73a28eae2a00 branch: 0.1 user: Alexandre Quessy date: Tue Jan 12 14:40:22 2010 -0500 summary: adding lunch.pid changeset: 148:8cc1caa99d69 branch: 0.1 parent: 84:903d08af2f1a user: Alexandre Quessy date: Tue Jan 12 14:38:31 2010 -0500 summary: Creating branch 0.1 changeset: 147:45ee2527bdee branch: 0.2 user: Alexandre Quessy date: Tue Jan 12 14:44:58 2010 -0500 summary: Creating branch 0.2 changeset: 146:14726268075f branch: py user: Alexandre Quessy date: Tue Jan 12 14:11:03 2010 -0500 summary: Added an error message when not able to connect using ssh changeset: 145:938bbd9fc58b branch: py user: Alexandre Quessy date: Tue Jan 12 13:54:42 2010 -0500 summary: fixed a bug with ssh changeset: 144:077110a079c6 branch: py user: Alexandre Quessy date: Tue Jan 12 13:52:23 2010 -0500 summary: Changed child log data prefix. changeset: 143:55a8afd0d333 branch: py user: Alexandre Quessy date: Tue Jan 12 12:07:58 2010 -0500 summary: added slave logging changeset: 142:914730048d1d branch: py user: Alexandre Quessy date: Tue Jan 12 09:17:26 2010 -0500 summary: Added tag 0.2.1 for changeset 1af7d071208d changeset: 141:1af7d071208d branch: py tag: 0.2.1 user: Alexandre Quessy date: Tue Jan 12 09:14:00 2010 -0500 summary: updated changelog changeset: 140:4524a8481976 branch: py user: Alexandre Quessy date: Tue Jan 12 09:12:30 2010 -0500 summary: improved backward-compatibility with scripts made with version 0.1 changeset: 139:9f5c7b6bf32a branch: py user: Alexandre Quessy date: Tue Jan 12 08:13:59 2010 -0500 summary: fixed formatting in pubkey changeset: 138:53e57b9ce668 branch: py user: Alexandre Quessy date: Tue Jan 12 08:11:30 2010 -0500 summary: fixed error with scripts dir name. changeset: 137:32cc57bc6651 branch: py user: Alexandre Quessy date: Tue Jan 12 08:10:05 2010 -0500 summary: changed twisted dep version changeset: 136:e5504b6ca4f8 branch: py user: Alexandre Quessy date: Tue Jan 12 08:07:20 2010 -0500 summary: updated versions to 0.2.0 changeset: 135:dc1930aea90e branch: py parent: 122:4a38c8bd1d45 parent: 134:dffc48c94537 user: Alexandre Quessy date: Tue Jan 12 08:03:24 2010 -0500 summary: merging with remote changes changeset: 134:dffc48c94537 branch: py user: Alexandre Quessy date: Mon Jan 11 17:32:51 2010 -0500 summary: Added tag 0.2 for changeset 1fe89ed9da01 changeset: 133:1fe89ed9da01 branch: py tag: 0.2.0 user: Alexandre Quessy date: Mon Jan 11 16:52:32 2010 -0500 summary: Huge progress in asynchronous iterating of groups at startup. changeset: 132:b197739bbfa0 branch: py user: Alexandre Quessy date: Mon Jan 11 15:18:17 2010 -0500 summary: mucho progress before i scrap this all for more improvements. changeset: 131:1b116e68924b branch: py user: Alexandre Quessy date: Mon Jan 11 14:04:33 2010 -0500 summary: Major changes and fixes in master. changeset: 130:83ccf79774b7 branch: py user: Alexandre Quessy date: Mon Jan 11 13:09:11 2010 -0500 summary: removing lunch/constants.py changeset: 129:cda24db07b25 branch: py user: Alexandre Quessy date: Mon Jan 11 13:08:39 2010 -0500 summary: Constants cleanup in master. changeset: 128:ac77d866597b branch: py user: Alexandre Quessy date: Mon Jan 11 13:00:13 2010 -0500 summary: changed callbacks from do_* to recv_* in slave. changeset: 127:98ce9c6d9f31 branch: py user: Alexandre Quessy date: Mon Jan 11 12:57:50 2010 -0500 summary: splitted callbacks in master. Messages to lowercase. changeset: 126:48dc2632a82f branch: py user: Alexandre Quessy date: Mon Jan 11 11:10:36 2010 -0500 summary: making sure process is dead after 5.0 seconds. changeset: 125:653d3ac04943 branch: py parent: 124:55aff2084f26 parent: 123:652cb86339b1 user: Alexandre Quessy date: Mon Jan 11 10:07:13 2010 -0500 summary: Merged recent changes together. changeset: 124:55aff2084f26 branch: py parent: 116:0d8e482610ae user: Alexandre Quessy date: Mon Jan 11 10:06:42 2010 -0500 summary: Added small comments. Things todo in Command changeset: 123:652cb86339b1 branch: py parent: 121:4839dcddbd88 user: Alexandre Quessy date: Sun Jan 10 10:18:49 2010 -0500 summary: lunch-master add lunch-slave in its $PATH when in devel mode. changeset: 122:4a38c8bd1d45 branch: py user: Alexandre Quessy date: Sat Jan 09 03:16:54 2010 -0500 summary: changed lunch executable to lunch-0.1 in lunch-0.1 changeset: 121:4839dcddbd88 branch: py user: Alexandre Quessy date: Sat Jan 09 02:53:06 2010 -0500 summary: added one test. Disabled logging which would occur in tests. changeset: 120:c4e568898ad0 branch: py user: Alexandre Quessy date: Sat Jan 09 02:37:43 2010 -0500 summary: adding a nice groups example changeset: 119:956350ab6230 branch: py user: Alexandre Quessy date: Sat Jan 09 02:29:34 2010 -0500 summary: fixed an error in master. changeset: 118:a3355cba5b34 branch: py user: Alexandre Quessy date: Sat Jan 09 02:26:32 2010 -0500 summary: moved examples to root changeset: 117:f5527bb0318e branch: py user: Alexandre Quessy date: Sat Jan 09 02:26:16 2010 -0500 summary: changed identifier for title in example changeset: 116:0d8e482610ae branch: py user: Alexandre Quessy date: Fri Jan 08 17:42:46 2010 -0500 summary: removed a hack that doesnt work, to change the slave process name to lunch-slave in ps. changeset: 115:aa0e2aaa2cbb branch: py user: Alexandre Quessy date: Fri Jan 08 17:41:18 2010 -0500 summary: Using bash or sh -c exec to run the child process. changeset: 114:5c455bd81fbc branch: py user: Alexandre Quessy date: Fri Jan 08 17:08:39 2010 -0500 summary: Got rid of constants in slave. No more deps ! changeset: 113:df11603cbbd9 branch: py user: Alexandre Quessy date: Fri Jan 08 16:36:07 2010 -0500 summary: Changed log dir to lunch_log changeset: 112:6c1c7c6287a8 branch: py user: Alexandre Quessy date: Fri Jan 08 16:29:57 2010 -0500 summary: Getting correctly the signal when child process is dead. changeset: 111:9cf5e9baad37 branch: py user: Alexandre Quessy date: Fri Jan 08 16:04:23 2010 -0500 summary: In slave, passing a line of text to do_* methods, instead of a list of words. changeset: 110:1822cf82fcd6 branch: py user: Alexandre Quessy date: Fri Jan 08 15:42:39 2010 -0500 summary: Progress in how the slave behaves. Removed STATE_ERROR in it. changeset: 109:3dc89db74ca6 branch: py user: Alexandre Quessy date: Fri Jan 08 15:21:42 2010 -0500 summary: Trying to send SIGINT and SIGKILL to processes to stop them. changeset: 108:18b0cb5f9be3 branch: py user: Alexandre Quessy date: Fri Jan 08 13:42:09 2010 -0500 summary: added a lot of send_* methods in slave. Renamed classes in slave. changeset: 107:6af31007d57d branch: py user: Alexandre Quessy date: Fri Jan 08 11:37:04 2010 -0500 summary: Dropped the need for sig.py in lunch-slave changeset: 106:c21427768849 branch: py user: Alexandre Quessy date: Fri Jan 08 10:47:34 2010 -0500 summary: Dropped JSON dependency changeset: 105:10c0eb4f9636 branch: py user: Alexandre Quessy date: Fri Jan 08 10:34:14 2010 -0500 summary: added license in gui file changeset: 104:f32ccd6fd6f5 branch: py user: Alexandre Quessy date: Thu Jan 07 18:11:18 2010 -0500 summary: added state diagram to doc changeset: 103:715513211fa4 branch: py user: Alexandre Quessy date: Thu Jan 07 18:08:41 2010 -0500 summary: added gui... Not connected to main app yet, though. changeset: 102:bd886332f44b branch: py user: Alexandre Quessy date: Thu Jan 07 14:03:35 2010 -0500 summary: Few fixes in the master to make it use a config file. changeset: 101:90385b6305b7 branch: py user: Alexandre Quessy date: Thu Jan 07 13:42:43 2010 -0500 summary: Updating script to find file given as arg. changeset: 100:dbf02a2f34bf branch: py user: Alexandre Quessy date: Thu Jan 07 11:47:10 2010 -0500 summary: Splitted executable and a module. That's to avoid starting logs and stuff in the beginning of the file. changeset: 99:49866990a327 branch: py user: Alexandre Quessy date: Thu Jan 07 11:39:24 2010 -0500 summary: Removed mplayer from the example. changeset: 98:fa97cfe83bb3 branch: py user: Alexandre Quessy date: Thu Jan 07 11:38:51 2010 -0500 summary: Fixed the make file. Added an example. changeset: 97:da9240d2fbed branch: py parent: 96:b026a91530d8 parent: 86:31c196fc5eef user: Alexandre Quessy date: Thu Jan 07 11:35:10 2010 -0500 summary: Merging with dead branch, which contains fixes on debian/changelog changeset: 96:b026a91530d8 branch: py user: Alexandre Quessy date: Thu Jan 07 11:33:39 2010 -0500 summary: Updated makefile and setup.py to match new version. changeset: 95:3c3ee7bf9ee3 branch: py user: Alexandre Quessy date: Thu Jan 07 11:31:10 2010 -0500 summary: Moved lunch-master to lunch changeset: 94:28108b6cd929 branch: py user: Alexandre Quessy date: Thu Jan 07 11:30:40 2010 -0500 summary: Removed old files from lunch 0.1 changeset: 93:f7852756e1df branch: py user: Alexandre Quessy date: Thu Jan 07 11:28:44 2010 -0500 summary: Put back lunch-0.1 in script. (from r72) changeset: 92:77a40237a878 branch: py user: Alexandre Quessy date: Thu Jan 07 11:26:18 2010 -0500 summary: Trying to make sure all slave are dead before to stop reactor. changeset: 91:b8926dfaf18a branch: py user: Alexandre Quessy date: Thu Jan 07 10:26:26 2010 -0500 summary: The master seems to work every time we try it. When the command dies quickly, it is not relaunched, though. changeset: 90:d2440d6ce91a branch: py user: Alexandre Quessy date: Thu Jan 07 08:51:09 2010 -0500 summary: More changes in new slave and master. Merging with changes in debian/changelog. changeset: 89:7324582e1fd0 branch: py user: Alexandre Quessy date: Wed Jan 06 21:01:41 2010 -0500 summary: added lunch-master which can launch slaves ! changeset: 88:bd8a0aa22927 branch: py user: Alexandre Quessy date: Wed Jan 06 18:41:04 2010 -0500 summary: Now making sure the process is stopped before quitting changeset: 87:3a1a6588d528 branch: py parent: 85:b18db3c12cb0 user: Alexandre Quessy date: Wed Jan 06 16:09:07 2010 -0500 summary: Added command-line options to launch-slave. changeset: 86:31c196fc5eef branch: py user: Alexandre Quessy date: Thu Jan 07 06:00:02 2010 -0500 summary: Fixed badly formatted trailing lines in debian/changelog. changeset: 85:b18db3c12cb0 branch: py user: Alexandre Quessy date: Wed Jan 06 15:45:24 2010 -0500 summary: Adding lunch-slave. A Twisted-driven interactive single process launcher. changeset: 84:903d08af2f1a branch: py user: Alexandre Quessy date: Wed Jan 06 12:01:58 2010 -0500 summary: Added tag 0.1.5 for changeset 4a8a4a9ed24c changeset: 83:4a8a4a9ed24c branch: py tag: 0.1.5 user: Alexandre Quessy date: Wed Jan 06 11:18:10 2010 -0500 summary: Changed executable to check if the directory it is in is "scripts" instead of looking for Mercurial files. changeset: 82:bc4dba201761 branch: py user: Alexandre Quessy date: Wed Jan 06 11:12:30 2010 -0500 summary: Added unit tests changeset: 81:6af627065d59 branch: py user: Alexandre Quessy date: Wed Jan 06 10:32:58 2010 -0500 summary: Added tag 0.1.4 for changeset 30c2df205258 changeset: 80:30c2df205258 branch: py tag: 0.1.4 user: Alexandre Quessy date: Wed Jan 06 10:29:45 2010 -0500 summary: Updated project URL. Preparing tag 0.1.4 changeset: 79:37e5302ca192 branch: py user: Alexandre Quessy date: Wed Jan 06 10:21:37 2010 -0500 summary: Had forgotten to had the package name to setup.py changeset: 78:775ab82f251b branch: py user: Alexandre Quessy date: Wed Jan 06 10:09:43 2010 -0500 summary: Removed useless english translation. changeset: 77:98b25a40a407 branch: py user: Alexandre Quessy date: Tue Jan 05 16:25:53 2010 -0500 summary: Updated README.txt to match no-so-recent changes. changeset: 76:dfbf9f4244b9 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 16:07:18 2010 -0500 summary: Added tag 0.1.3 for changeset 1d9498d5f77b changeset: 75:1d9498d5f77b branch: py tag: 0.1.3 user: Alexandre Quessy on bzzz date: Tue Jan 05 16:07:08 2010 -0500 summary: updated classes again. fixed typo. changeset: 74:b97fd19441a7 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 16:02:12 2010 -0500 summary: updated class diagram changeset: 73:b2193749f792 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 15:58:11 2010 -0500 summary: Splitting lunch into 4 main files. changeset: 72:14234029c4b3 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 15:02:26 2010 -0500 summary: Fix in the version number. changeset: 71:5454791b9486 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 14:40:20 2010 -0500 summary: Moved lunch to bin/lunch. Updated version to 0.1.3. Generating PNG on the fly with imagemagick changeset: 70:384fc6e6510d branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 14:23:41 2010 -0500 summary: rm ssh-keyinstall changeset: 69:faa9172e3758 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 14:22:58 2010 -0500 summary: added *ps to ignores changeset: 68:9e2639d1c285 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 14:22:33 2010 -0500 summary: added classes.dia changeset: 67:b751f981c4ac branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 10:51:40 2010 -0500 summary: added .hgignore changeset: 66:49f892257010 branch: py user: Alexandre Quessy on bzzz date: Tue Jan 05 10:48:05 2010 -0500 summary: Fixed typo in setup.py. Added ssh-keyinstall bash script from http://svn.sat.qc.ca/trac/postures/browser/trunk/scripts/ssh-keyinstall at r31. changeset: 65:2627a2103adb branch: py user: alexandre date: Fri Dec 18 14:15:31 2009 -0500 summary: [svn] Added log-file option to slave. The add_command function has a log_file keyword argument. changeset: 64:5754679c7c7e branch: py user: alexandre date: Thu Nov 26 16:09:29 2009 -0500 summary: [svn] added deb target in makefile changeset: 63:317fe8ad3a16 branch: py user: alexandre date: Thu Nov 26 16:04:34 2009 -0500 summary: [svn] added setup for deb packaging changeset: 62:1a6753ea5c74 branch: py user: alexandre date: Thu Nov 26 15:51:38 2009 -0500 summary: [svn] fixing stuff so that we can create a deb file changeset: 61:b6c197a7bf58 branch: py user: alexandre date: Thu Nov 26 15:46:24 2009 -0500 summary: [svn] added something to prevent error when upgrading changeset: 60:14f328ebbc0f branch: py user: alexandre date: Thu Nov 26 15:44:56 2009 -0500 summary: [svn] fixed error in .desktop changeset: 59:7ff98a7f6412 branch: py user: alexandre date: Thu Nov 26 15:43:13 2009 -0500 summary: [svn] renamed icon adn desktop file to lowercase. Added debian files. changeset: 58:c08649026b00 branch: py user: alexandre date: Thu Nov 26 15:27:52 2009 -0500 summary: [svn] changed default lunch config file to ~/.lunchrc changeset: 57:8f3b785d6c94 branch: py user: alexandre date: Thu Nov 26 15:17:24 2009 -0500 summary: [svn] Re-added the possibility to provide config file as arg without -f option. changeset: 56:54bd33545ae3 branch: py user: mikewoz date: Thu Nov 26 14:10:16 2009 -0500 summary: [svn] moved lunch config scripts out of py folder, and cleaned up a bit changeset: 55:d07aa32e1112 branch: py user: mikewoz date: Thu Nov 26 14:03:13 2009 -0500 summary: [svn] fixed no_resurrect bug (to no_respawn) changeset: 54:5fdfd68218b6 branch: py user: simonp date: Thu Nov 26 12:56:55 2009 -0500 summary: [svn] Added a respawn option for add_command changeset: 53:9dc35d400651 branch: py user: simonp date: Wed Nov 25 18:11:44 2009 -0500 summary: [svn] Now lunch use xterm changeset: 52:123e1fd35d1b branch: py user: simonp date: Tue Nov 24 18:33:05 2009 -0500 summary: [svn] Simplified config-file option handling (and made it to work) changeset: 51:06343de33533 branch: py user: mikewoz date: Tue Nov 17 20:24:58 2009 -0500 summary: [svn] changes to configs changeset: 50:096a0bbf7a37 branch: py user: mikewoz date: Fri Nov 06 14:21:06 2009 -0500 summary: [svn] changeset: 49:971515181d9b branch: py user: alexandre date: Fri Oct 30 15:54:21 2009 -0400 summary: [svn] had forgotten an import changeset: 48:7ac87a1aecd1 branch: py user: alexandre date: Fri Oct 30 15:53:49 2009 -0400 summary: [svn] bigger default sleep between each launching. changeset: 47:0b6926e86046 branch: py user: alexandre date: Mon Oct 26 17:05:09 2009 -0400 summary: [svn] clean up man page in make file changeset: 46:e4f8f279cec3 branch: py user: alexandre date: Mon Oct 26 17:03:04 2009 -0400 summary: [svn] Removing the slave's logfile. Added makefile for lunch. changeset: 45:92a30e10e4ca branch: py user: alexandre date: Mon Oct 26 16:44:52 2009 -0400 summary: [svn] fixed fatal error in lunch. Was trying to close never opened file. changeset: 44:bd076faaa5d5 branch: py user: alexandre date: Mon Oct 26 16:32:35 2009 -0400 summary: [svn] added log files in lunch slaves changeset: 43:f039bd1ba09f branch: py user: mikewoz date: Fri Oct 23 21:01:46 2009 -0400 summary: [svn] changeset: 42:926fbc971c87 branch: py user: mikewoz date: Thu Oct 22 19:45:27 2009 -0400 summary: [svn] added config-audiotest changeset: 41:ec6e0a8715c6 branch: py user: mikewoz date: Wed Oct 14 16:24:56 2009 -0400 summary: [svn] removed LD_LIBRARY_PATH changeset: 40:35944bbfca25 branch: py user: alexandre date: Wed Oct 07 10:21:37 2009 -0400 summary: [svn] lunch is now much more verbose. Possible to provide config file as an arg. changeset: 39:55e6533212f3 branch: py user: mikewoz date: Tue Oct 06 21:32:56 2009 -0400 summary: [svn] updates to lunch config files changeset: 38:2127ec23c9ee branch: py user: mikewoz date: Tue Oct 06 15:54:19 2009 -0400 summary: [svn] hint as to how lunch should stop milhouses changeset: 37:bf633863c1b7 branch: py user: mikewoz date: Thu Oct 01 18:53:30 2009 -0400 summary: [svn] added some new lunch configs for testing spin changeset: 36:2bda51920536 branch: py user: mikewoz date: Thu Oct 01 17:41:55 2009 -0400 summary: [svn] removed spin program aliases changeset: 35:7138f0c60424 branch: py user: alexandre date: Tue Sep 01 16:39:55 2009 -0400 summary: [svn] updated lunch make install changeset: 34:7e2a4bc8e404 branch: py user: alexandre date: Tue Sep 01 10:20:48 2009 -0400 summary: [svn] updated lunch so that it doesnt crash due to multiprocessing version issues (?) changeset: 33:8b10bf14e022 branch: py user: alexandre date: Mon Aug 10 02:44:12 2009 -0400 summary: [svn] adding comment to config-postures changeset: 32:94f260eed125 branch: py user: mikewoz date: Fri Aug 07 17:32:38 2009 -0400 summary: [svn] mike's config for lunch changeset: 31:5f04392a3db9 branch: py user: mikewoz date: Fri Aug 07 14:48:13 2009 -0400 summary: [svn] changed name of vessServer to just vess changeset: 30:0805e6b3d180 branch: py user: alexandre date: Mon Jul 27 13:28:56 2009 -0400 summary: [svn] corrected svg icon path changeset: 29:c3f34905874e branch: py user: alexandre date: Fri Jul 24 21:48:55 2009 -0400 summary: [svn] cleaner make file. changeset: 28:373aa3e2d9fa branch: py user: alexandre date: Fri Jul 24 21:10:25 2009 -0400 summary: [svn] Fixed a sprintf parsing. Improved doc. Type make doc. changeset: 27:3485d126ac29 branch: py user: mikewoz date: Fri Jul 24 16:41:15 2009 -0400 summary: [svn] added an icon for Lunch changeset: 26:dc1cf939cadd branch: py user: mikewoz date: Fri Jul 24 16:28:51 2009 -0400 summary: [svn] updated doc changeset: 25:9e2e9695ff82 branch: py user: mikewoz date: Fri Jul 24 16:27:55 2009 -0400 summary: [svn] adding Lunch.desktop changeset: 24:7fd30b4c777c branch: py user: alexandre date: Fri Jul 24 14:42:29 2009 -0400 summary: [svn] updated lunch with trivial stuff. changeset: 23:7f4f048ba671 branch: py user: alexandre date: Thu Jul 23 17:21:02 2009 -0400 summary: [svn] Sorting as been sorted out. changeset: 22:133727df91b1 branch: py user: mikewoz date: Thu Jul 23 16:28:22 2009 -0400 summary: [svn] upodated lunch changeset: 21:b07972f065d7 branch: py user: mikewoz date: Thu Jul 23 15:36:20 2009 -0400 summary: [svn] postures lunch config draft. todos in lunch changeset: 20:9004b7bbd790 branch: py user: alexandre date: Thu Jul 23 14:08:30 2009 -0400 summary: [svn] doc update changeset: 19:4d4721c167ff branch: py user: alexandre date: Wed Jul 22 15:45:16 2009 -0400 summary: [svn] updated sample conf changeset: 18:cb18f48c0dc8 branch: py user: alexandre date: Wed Jul 22 15:39:31 2009 -0400 summary: [svn] moved old launcher stuff to prototypes changeset: 17:de4074e1400a branch: py user: alexandre date: Wed Jul 22 14:28:54 2009 -0400 summary: [svn] finally fixed more bugs. more docs. Still need to use argv for the command line instead of quotes, i guess. changeset: 16:4b09804c32ba branch: py user: alexandre date: Wed Jul 22 12:35:40 2009 -0400 summary: [svn] improved doc changeset: 15:bb9784f8ef3f branch: py user: alexandre date: Wed Jul 22 12:24:22 2009 -0400 summary: [svn] lunch works like a charm. No more traceback on ctrl-C. Updated config-sample changeset: 14:2b4e17397209 branch: py user: alexandre date: Wed Jul 22 11:04:36 2009 -0400 summary: [svn] merged new_lunch to lunch changeset: 13:209d0aa716eb branch: py user: alexandre date: Wed Jul 22 11:03:07 2009 -0400 summary: [svn] fixed quote but in the command arg we passed to new_lunch changeset: 12:14c01fee8418 branch: py user: alexandre date: Wed Jul 22 08:37:54 2009 -0400 summary: [svn] new_Lunch now works. needs more testing. will be merged to lunch changeset: 11:9beb52494fab branch: py user: alexandre date: Tue Jul 21 23:15:21 2009 -0400 summary: [svn] adding new_lunch as a test. changeset: 10:91627edd96cd branch: py user: alexandre date: Mon Jul 20 21:10:14 2009 -0400 summary: [svn] few comments. some light modifs changeset: 9:f17bcee33033 branch: py user: alexandre date: Mon Jul 20 20:48:50 2009 -0400 summary: [svn] lunch has few fixes that might help some day to prevent from traceback to be printed when ctrl-c is hit changeset: 8:e1da073e5bfc branch: py user: alexandre date: Mon Jul 20 20:38:40 2009 -0400 summary: [svn] fixed forever-resurrect bug that i added in rev **45 changeset: 7:f86a8a2e010b branch: py user: alexandre date: Mon Jul 20 20:26:44 2009 -0400 summary: [svn] addinf config sample changeset: 6:faacb86be35b branch: py user: alexandre date: Mon Jul 20 20:23:04 2009 -0400 summary: [svn] lunch works but we still have crappy output when ctrl-c is pressed changeset: 5:4df960bdcf19 branch: py user: alexandre date: Mon Jul 20 18:15:13 2009 -0400 summary: [svn] moved new script changeset: 4:07c54aa015e5 branch: py user: alexandre date: Mon Jul 20 18:14:59 2009 -0400 summary: [svn] moved old script changeset: 3:0a949aff9696 branch: py user: alexandre date: Mon Jul 20 17:39:57 2009 -0400 summary: [svn] small mods changeset: 2:17f87f5a1b9a branch: py user: alexandre date: Mon Jul 20 17:33:02 2009 -0400 summary: [svn] some changes on the launcher thingy changeset: 1:3c1c27dd3316 branch: py user: alexandre date: Mon Jul 20 16:06:27 2009 -0400 summary: [svn] not working but in progress changeset: 0:c42d76a459f5 branch: py user: alexandre date: Fri Jul 17 17:35:04 2009 -0400 summary: [svn] adding a non-working work version of py/* lunch-0.4.0/INSTALL0000644000000000000000000000136311437735014012363 0ustar rootrootINSTALLATION ============ You will need a few software. On Ubuntu or Debian GNU/Linux, install them using the following command :: sudo apt-get install openssh-client openssh-server python-setuptools help2man python-twisted-core python-gtk2 Install lunch to /usr/local/bin/lunch on both local and remote hosts:: make sudo make install There should be a Lunch icon in the Application/Other Gnome menu. Read the README file or read the lunch (1) man page to know how to use it. DOCUMENTATION ============= You can generate HTML out of the README file using rst2html:: rst2html README readme.html Pydoc can generate HTML documentation out of the Python script:: pydoc -w ./lunch See the Makefile for more installation options:: make doc lunch-0.4.0/examples/0000755000000000000000000000000011437735014013145 5ustar rootrootlunch-0.4.0/examples/remote_through_ssh.lunch0000755000000000000000000000072411437735014020116 0ustar rootroot#!/usr/bin/env lunch # In this example, we run commands on a remote host, via SSH. # YOu should change the value of the USER and HOST variables below. HOST = "example.org" USER = "johndoe" # set USER to None if the user name is the same than your local user name: #USER = None add_command("xeyes", env={"DISPLAY":":0.0"}, user=USER, host=HOST, identifier="remote_xeyes") add_command("xlogo", env={"DISPLAY":":0.0"}, user=USER, host=HOST, identifier="remote_xlogo") lunch-0.4.0/examples/dependencies_advanced.lunch0000755000000000000000000000105011437735014020452 0ustar rootroot#!/usr/bin/env lunch # This example demonstrates dependencies. # It also uses the strenghts of the Python programming language. command = """xterm -geometry 30x4 -hold -title %s -e echo '%s'""" add_command(command % ('once', "ran only once"), respawn=False) add_command(command % ('a', "a"), identifier="a") add_command(command % ('b', "b depends on a"), identifier="b", depends="a") add_command(command % ('c', "c depends on a"), identifier="c", depends="a") add_command(command % ('d', "d depends on b and c"), identifier="d", depends=["b", "c"]) lunch-0.4.0/examples/simple.lunch0000755000000000000000000000023011437735014015467 0ustar rootroot#!/usr/bin/env lunch add_command("/usr/lib/xscreensaver/polytopes") add_command("glxgears") add_command("xlogo") add_command("xeyes -geometry 240x320") lunch-0.4.0/examples/dependencies.lunch0000755000000000000000000000116211437735014016631 0ustar rootroot#!/usr/bin/env lunch # This example demonstrates dependencies. # A bunch of processes depending on the xeyes: add_command("xeyes", identifier="xeyes") add_command("xcalc", identifier="xcalc", depends="xeyes") add_command("xclock", identifier="xclock", depends="xeyes") add_command("xlogo", identifier="xlogo", depends="xeyes") # A chain of dependencies: add_command("xconsole", identifier="xconsole", sleep_after=1.0) add_command("xman", identifier="xman", depends="xconsole") add_command("xman", identifier="xman2", depends="xman") # A process that depends on two: add_command("glxgears", depends=["xlogo", "xconsole"]) lunch-0.4.0/examples/sleeping_after.lunch0000755000000000000000000000025611437735014017175 0ustar rootroot#!/usr/bin/env lunch # starts 3 xeyes, sleeps 2 seconds, and start xlogo add_command("xeyes") add_command("xeyes") add_command("xeyes", sleep_after=2.0) add_command("xlogo") lunch-0.4.0/examples/using_the_library/0000755000000000000000000000000011437735014016656 5ustar rootrootlunch-0.4.0/examples/using_the_library/simple.py0000755000000000000000000000200711437735014020523 0ustar rootroot#!/usr/bin/env python """ This example demonstrate how to use lunch master as a library. """ from twisted.internet import gtk2reactor gtk2reactor.install() # has to be done before importing reactor from twisted.internet import reactor from twisted.internet import task from lunch import commands from lunch import master from lunch import gui if __name__ == "__main__": unique_master_id = "example" log_dir = master.DEFAULT_LOG_DIR pid_file = master.write_master_pid_file(identifier=unique_master_id, directory=log_dir) # XXX add_command here m = master.Master(log_dir=log_dir, pid_file=pid_file) m.add_command(commands.Command("xeyes", identifier="xeyes")) m.add_command(commands.Command("xlogo", identifier="xlogo")) m.add_command(commands.Command("xcalc", identifier="xcalc")) def _test(): print("Adding one more!") m.add_command(commands.Command("xeyes")) looping_call = task.LoopingCall(_test) looping_call.start(1.0, False) gui.start_gui(m) reactor.run() lunch-0.4.0/examples/using_the_library/add_remove.py0000755000000000000000000000360111437735014021340 0ustar rootroot#!/usr/bin/env python """ This example demonstrate how to use lunch master as a library. """ from twisted.internet import gtk2reactor gtk2reactor.install() # has to be done before importing reactor from twisted.internet import reactor from twisted.internet import task from lunch import commands from lunch import master from lunch import gui has_them = False counter = 0 if __name__ == "__main__": unique_master_id = "example" log_dir = master.DEFAULT_LOG_DIR master.start_logging() pid_file = master.write_master_pid_file(identifier=unique_master_id, directory=log_dir) m = master.Master(log_dir=log_dir, pid_file=pid_file, verbose=True) m.add_command(commands.Command("xeyes", identifier="xeyes")) m.add_command(commands.Command("xlogo", identifier="xlogo")) m.add_command(commands.Command("xcalc", identifier="xcalc")) m.add_command(commands.Command("xterm -hold -e /bin/bash -c \"echo %d\"" % (counter), identifier="xterm")) counter += 1 has_them = True def _test(): global has_them global counter if not has_them: print("Adding them again!") m.add_command(commands.Command("xeyes", identifier="xeyes")) m.add_command(commands.Command("xlogo", identifier="xlogo")) m.add_command(commands.Command("xcalc", identifier="xcalc")) m.add_command(commands.Command("xterm -hold -e /bin/bash -c \"echo %d\"" % (counter), identifier="xterm")) counter += 1 has_them = True else: print("Removing them.") m.remove_command("xeyes") m.remove_command("xlogo") m.remove_command("xcalc") m.remove_command("xterm") has_them = False # a GUI! app = gui.start_gui(m) looping_call = task.LoopingCall(_test) looping_call.start(3.0, False) reactor.run() lunch-0.4.0/examples/potential_errors.lunch0000755000000000000000000000032111437735014017572 0ustar rootroot#!/usr/bin/env lunch # In this example, we test a non-existing command should not be called again, # since it has a minimum lifetime of 0.5. (default) add_command("non-existing-command") add_command("xeyes") lunch-0.4.0/examples/screensavers.lunch0000755000000000000000000000102511437735014016704 0ustar rootroot#!/usr/bin/env lunch # lunch example config file. # In this example, a lot of CPU-intensive commands are ran. add_command("/usr/lib/xscreensaver/polytopes") add_command("/usr/lib/xscreensaver/solarwinds") add_command("/usr/lib/xscreensaver/hufo_smoke") add_command("/usr/lib/xscreensaver/euphoria") add_command("/usr/lib/xscreensaver/flux") add_command("/usr/lib/xscreensaver/flyingtoasters") add_command("/usr/lib/xscreensaver/glblur") add_command("/usr/lib/xscreensaver/moebiusgears") add_command("/usr/lib/xscreensaver/plasma") lunch-0.4.0/examples/ssh_with_nondefault_port.lunch0000755000000000000000000000045511437735014021322 0ustar rootroot# How course, you should change the user, host options, as well the the XAUTHORITY environment variable to suit your setup. add_command("xeyes", identifier="xeyes", env={"DISPLAY": ":0.0", "XAUTHORITY": "/var/run/gdm3/auth-for-aalex-ijt1LA/database"}, user="aalex", host="192.168.1.4", ssh_port=23) lunch-0.4.0/examples/spawn_only_once.lunch0000755000000000000000000000055711437735014017407 0ustar rootroot#!/usr/bin/env lunch # In this example, some commands are ran only once. add_command("ls -l", identifier="ls", respawn=False) add_command("xeyes", identifier="xeyes", respawn=False) add_command("ps aux | grep -v PID | awk '{print $2}' | uniq | xargs -ia ls -l /proc/a/fd 2>&1 | grep -v 'total' | wc -l", identifier="total_num_file_descriptors_open", respawn=False) lunch-0.4.0/lunch/0000755000000000000000000000000011437735014012440 5ustar rootrootlunch-0.4.0/lunch/logger.py0000644000000000000000000001246611437735014014302 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Scenic # Copyright (C) 2008 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Scenic is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Scenic. If not, see . """ Python logging utility. Wraps the logging module and Twisted's python.log module. Now with non-blocking output """ import logging import sys import twisted # for its version import twisted.python.log as twisted_log from twisted.internet import fdesc #TODO: Specify the level by output ENABLE_NON_BLOCKING_OUTPUT = False SYSTEMWIDE_LOG_FILE_NAME = None SYSTEMWIDE_TO_FILE = False SYSTEMWIDE_TO_STDOUT = True SYSTEMWIDE_LEVEL = "warning" def start(level=None, name="twisted", to_stdout=None, to_file=None, log_file_name=None): """ Starts the logging for a single module. Each module should import this logging module and decide its level. The first time this is called, don't give any argument. It will log everything with the name "twisted". The programmer can choose the level from which to log, discarding any message with a lower level. Example : If level is INFO, the DEBUG messages (lower level) will not be displayed but the CRITICAL ones will. @param level: debug, info, error, warning or critical @type level: str @param to_stdout: Whether it should be printed to stdout. (False to disable) @param to_file: Whether it should be printed to file. (True to enable) @param name: What string to prefix with. @rtype: L{twisted.python.logging.PythonLoggingObserver} """ global SYSTEMWIDE_TO_STDOUT global SYSTEMWIDE_TO_FILE global SYSTEMWIDE_LOG_FILE_NAME global SYSTEMWIDE_LEVEL if log_file_name is not None: SYSTEMWIDE_LOG_FILE_NAME = log_file_name if to_file is True: SYSTEMWIDE_TO_FILE = True if level is not None: SYSTEMWIDE_LEVEL = level logger = logging.getLogger(name) formatter = logging.Formatter('%(asctime)s %(name)-13s %(levelname)-8s %(message)s') if SYSTEMWIDE_LEVEL is None: raise RuntimeError("You must specify a logging level. It's a string.") set_level(SYSTEMWIDE_LEVEL, name) if to_stdout is True or to_stdout is False: SYSTEMWIDE_TO_STDOUT = to_stdout if to_file is True or to_file is False: SYSTEMWIDE_TO_FILE = to_file #if log_file_name is None: # raise RuntimeError("You want to log to a file but the log file name is not set.") if SYSTEMWIDE_TO_STDOUT: so_handler = logging.StreamHandler(sys.stdout) if ENABLE_NON_BLOCKING_OUTPUT: fdesc.setNonBlocking(so_handler.stream) # NON-BLOCKING OUTPUT so_handler.setFormatter(formatter) logger.addHandler(so_handler) if SYSTEMWIDE_TO_FILE: if SYSTEMWIDE_LOG_FILE_NAME is None: raise RuntimeError("The log file name has not been set.") # file_handler = logging.FileHandler(log_file_name, mode='a', encoding='utf-8') file_handler = logging.FileHandler(SYSTEMWIDE_LOG_FILE_NAME) # FIXME: not catching IOError that could occur. if ENABLE_NON_BLOCKING_OUTPUT: fdesc.setNonBlocking(file_handler.stream) # NON-BLOCKING OUTPUT file_handler.setFormatter(formatter) logger.addHandler(file_handler) if name == 'twisted': observer = twisted_log.PythonLoggingObserver(name) observer.start() return logging.getLogger(name) else: return logging.getLogger(name) def stop(): """ Stops logging for a single module. """ logging.shutdown() def set_level(level, logger='twisted'): """ Sets the logging level for a single file. """ # It is totally useless to be able to change dynamically the logging level. #TODO: Merge with start() levels = { 'critical':logging.CRITICAL, # 50 'error':logging.ERROR, # 40 'warning':logging.WARNING, # 30 'info':logging.INFO, # 20 'debug':logging.DEBUG, # 10 } logger = logging.getLogger(logger) if level in levels: logger.setLevel(levels[level]) else: raise RuntimeError("%s is not a valid log level." % (level)) #ERR ? def critical(msg): """ Logs a message with CRITICAL level. (highest) """ twisted_log.msg(msg, logLevel=logging.CRITICAL) def error(msg): """ Logs a message with ERROR level. (2nd) """ twisted_log.msg(msg, logLevel=logging.ERROR) def warning(msg): """ Logs a message with WARNING level. (3rd) """ twisted_log.msg(msg, logLevel=logging.WARNING) def info(msg): """ Logs a message with INFO level. (4th) """ twisted_log.msg(msg) def debug(msg): """ Logs a message with DEBUG level. (5th and last level) """ twisted_log.msg(msg, logLevel=logging.DEBUG) lunch-0.4.0/lunch/gui.py0000644000000000000000000006631511437735014013611 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ Main GUI of the Lunch Master """ if __name__ == "__main__": from twisted.internet import gtk2reactor gtk2reactor.install() # has to be done before importing reactor from twisted.internet import reactor from twisted.internet import defer from twisted.internet import utils from twisted.python import procutils import gtk import pango import sys import os import textwrap import webbrowser from lunch import __version__ from lunch import dialogs from lunch.states import * from lunch import logger #TODO: i18nize def _(value): return value log = logger.start(name="lunch-gui") __license__ = _("""Lunch Copyright (C) 2009 Society for Arts and Technology (SAT) http://www.sat.qc.ca All rights reserved. This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Lunch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lunch. If not, see .""") PADDING_IN_TEXTVIEW = 22 # number of spaces before text contents in the textview ICON_FILE = "/usr/share/pixmaps/lunch.png" def run_once(executable, *args): """ Runs a command, without looking at its output or return value. Returns a Deferred or None. """ def _cb(result): #print(result) pass try: executable = procutils.which(executable)[0] except IndexError: log.error("Could not find executable %s" % (executable)) return None else: log.info("$ %s %s" % (executable, " ".join(list(args)))) d = utils.getProcessValue(executable, args, os.environ, '.', reactor) d.addCallback(_cb) return d def open_path(path): """ Opens a directory or file using gnome-open. Returns a Deferred or None. """ return run_once("gnome-open", path) def tail_child_log(command): """ Opens a terminal window with the tail of the log file for the child process of a command. Uses SSH if needed. @param command: L{lunch.commands.Command} """ #TODO: really need to make a valid path in add_command and keep it. child_log_path = os.path.join(command.child_log_dir, "child-%s.log" % (command.identifier)) xterm_title = 'tail -F %s' % (command.identifier) cmd = [] cmd.extend(["xterm", "-title", '%s' % (xterm_title), "-e"]) if command.host is not None: # using SSH cmd.extend(["ssh"]) if command.user is not None: cmd.extend(["-l", command.user]) cmd.extend([command.host]) xterm_title += " on " + command.host cmd.extend(["tail", "-F", child_log_path]) log.info("$ %s" % (" ".join(cmd))) run_once(*cmd) def tail_master_log(master): log_path = master.log_file if log_path is None: log.warning("No master log file to tail -F") # TODO: error dialog. else: cmd = [] xterm_title = "tail -F Lunch Master Log File" cmd.extend(["xterm", "-title", '%s' % (xterm_title), "-e"]) cmd.extend(["tail", "-F", log_path]) log.info("$ %s" % (" ".join(cmd))) run_once(*cmd) def man_lunch(): cmd = [] xterm_title = "man lunch" cmd.extend(["xterm", "-title", '%s' % (xterm_title), "-e"]) cmd.extend(["man", "lunch"]) log.info("$ %s" % (" ".join(cmd))) run_once(*cmd) def _format_command_line(text): """ Formats the text of a command in order to display it in a gtk.TextView """ global PADDING_IN_TEXTVIEW padding = PADDING_IN_TEXTVIEW + 2 width = 70 lines = textwrap.wrap(text, width) for i in range(len(lines)): if i == 0: lines[i] = "$ " + lines[i] else: lines[i] = "%s%s" % (" " * padding, lines[i]) # padding some spaces before the subsequent lines # log.debug("_format_command_line %s" % (lines)) return "\n".join(lines) class About(object): """ About dialog """ def __init__(self): self.about_dialog = gtk.AboutDialog() def show_about_dialog(self): global ICON_FILE self.about_dialog.set_name('Lunch') self.about_dialog.set_role('about') self.about_dialog.set_version(__version__) commentlabel = _('Simple Process Launcher for Complex Launching Setup.') self.about_dialog.set_comments(commentlabel) self.about_dialog.set_copyright(_("Copyright 2009-2010 Society for Arts and Technology")) self.about_dialog.set_license(__license__) self.about_dialog.set_authors([ 'Alexandre Quessy ' ]) self.about_dialog.set_documenters([ 'Simon Piette ' ]) self.about_dialog.set_artists(['Rocket000']) gtk.about_dialog_set_url_hook(self.show_website) self.about_dialog.set_website("http://svn.sat.qc.ca/trac/lunch") if not os.path.exists(ICON_FILE): log.warning("Could not find icon file %s." % (ICON_FILE)) else: large_icon = gtk.gdk.pixbuf_new_from_file(ICON_FILE) self.about_dialog.set_logo(large_icon) # Connect to callbacks self.about_dialog.connect('response', self.destroy_about) self.about_dialog.connect('delete_event', self.destroy_about) self.about_dialog.connect("delete-event", self.destroy_about) self.about_dialog.show_all() def show_website(self, widget, data): webbrowser.open(data) def destroy_about(self, *args): self.about_dialog.destroy() class LunchApp(object): """ Simple GTK2 GUI for Lunch Master. Defines the main window """ IDENTIFIER_COLUMN = 0 # the row in the treeview that contains the command identifier. def __init__(self, lunch_master=None): global ICON_FILE self.master = lunch_master self.confirm_close = True # should we ask if the user is sure to close the app? _commands = self.master.get_all_commands() # ------------------------------------------------------ # Window and its icon self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Lunch") self.window.connect("delete-event", self.destroy_app) #self.window.connect("destroy", self.destroy_app) WIDTH = 640 HEIGHT = 480 self.window.set_default_size(WIDTH, HEIGHT) if not os.path.exists(ICON_FILE): log.warning("Could not find icon file %s." % (ICON_FILE)) basename = os.path.basename(ICON_FILE) directory = os.path.dirname(__file__) parent_dir = "/".join(directory.split("/")[0:-1]) ICON_FILE = os.path.join(parent_dir, basename) log.warning("Using icon file %s" % (ICON_FILE)) if os.path.exists(ICON_FILE): icon = gtk.gdk.pixbuf_new_from_file(ICON_FILE) self.window.set_icon_list(icon) else: log.warning("Could not find icon file %s." % (ICON_FILE)) log.warning("Warning: Could not find icon file %s." % (ICON_FILE)) # Vertical Box vbox = gtk.VBox(homogeneous=False) self.window.add(vbox) # ------------------------------------------------------ # Menu bar self.ui_manager = None self.menubar = self._create_main_menu(self.window) vbox.pack_start(self.menubar, expand=False, fill=False) self.menubar.show() vpaned = gtk.VPaned() vbox.pack_start(vpaned, expand=True, fill=True) # ------------------------------------------------------ # Scrollable with a TreeView scroller = gtk.ScrolledWindow() scroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) scroller.set_shadow_type(gtk.SHADOW_IN) scroller.set_size_request(-1, 250) frame1 = gtk.Frame(label=_("Processes")) frame1.set_shadow_type(gtk.SHADOW_ETCHED_IN) frame1.add(scroller) vpaned.add1(frame1) # The ListStore contains the data. list_store = gtk.ListStore(str, str, str, int, str) # The TreeModelSort sorts the data self.model_sort = gtk.TreeModelSort(list_store) # The TreeView displays the sorted data in the GUI. self.tree_view_widget = gtk.TreeView(self.model_sort) self._setup_treeview() # self.tree_view_widget.set_property("has-tooltip", True) self.tree_view_widget.get_selection().connect("changed", self.on_selected_command_changed) #self.tree_view_widget.connect("query-tooltip", self.on_treeview_tooltip_queried) scroller.add(self.tree_view_widget) for command in _commands: self._add_command_in_tree(command) self.master.command_added_signal.connect(self.on_command_added) self.master.command_removed_signal.connect(self.on_command_removed) # ------------------------------------------------------ # TextView for the details scroller2 = gtk.ScrolledWindow() scroller2.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) scroller2.set_shadow_type(gtk.SHADOW_ETCHED_IN) scroller2.set_size_request(-1, 50) viewport = gtk.Viewport() frame2 = gtk.Frame(label=_("Details")) frame2.set_shadow_type(gtk.SHADOW_ETCHED_IN) frame2.add(scroller2) vpaned.add(frame2) #vbox.pack_start(scroller2, expand=False, fill=True) self.textview_widget = gtk.TextView() self._set_textview_appearance() scroller2.add(viewport) viewport.add(self.textview_widget) # ------------------------------------------------------ # Box with buttons. hbox = gtk.HBox(homogeneous=True) vbox.pack_start(hbox, expand=False, fill=False) self.openlog_button_widget = gtk.Button(_("Open child process log file")) self.openlog_button_widget.connect("clicked", self.on_openlog_clicked) hbox.pack_start(self.openlog_button_widget) self.stop_command_button_widget = gtk.Button(_("Stop child process")) self.stop_command_button_widget.connect("clicked", self.on_stop_command_clicked) hbox.pack_start(self.stop_command_button_widget) self.start_command_button_widget = gtk.Button(_("Start child process")) self.start_command_button_widget.connect("clicked", self.on_start_command_clicked) hbox.pack_start(self.start_command_button_widget) self.window.show_all() def _set_textview_appearance(self): self.textview_widget.set_editable(False) textview_buffer = self.textview_widget.get_buffer() textview_buffer.create_tag("font", family="Monospace", scale=pango.SCALE_SMALL) def set_textview_text(self, text): textview_buffer = self.textview_widget.get_buffer() textview_buffer.delete(*textview_buffer.get_bounds()) textview_buffer.insert_with_tags_by_name(textview_buffer.get_start_iter(), text, "font") #textview_buffer.set_text(text) def _update_text_in_textview(self): command = self._get_currently_selected_command(False) if command is None: txt = _("Select a command to view information about it.") else: txt = "" keyval = [ (_("identifier"), command.identifier), (_("command"), _format_command_line(command.command)), (_("child_pid"), command.child_pid), (_("depends"), command.depends), (_("enabled"), command.enabled), (_("user"), command.user), (_("host"), command.host), (_("env"), command.env), #(_("log_dir"), command.log_dir), (_("respawn"), command.respawn), (_("sleep_after"), command.sleep_after), (_("delay_before_kill"), command.delay_before_kill), (_("verbose"), command.verbose), (_("how_many_times_tried"), command.how_many_times_tried), (_("_has_shown_ssh_error"), command._has_shown_ssh_error), (_("gave_up"), command.gave_up), ] for key, val in keyval: _format = "%" + ("%ds" % PADDING_IN_TEXTVIEW) + ": %s\n" txt += _format % (key, val) self.set_textview_text(txt) def _update_text_in_textview_if_command_is_selected(self, command): if command == self._get_currently_selected_command(False): self._update_text_in_textview() #def on_treeview_tooltip_queried(self, *args): # log.debug("on_treeview_tooltip_queried %s" % (str(args))) def on_command_added(self, command): log.debug("on_command_added") self._add_command_in_tree(command) self._update_text_in_textview() def on_command_removed(self, command): log.debug("on_command_removed") self._remove_command_from_tree(command) self._update_text_in_textview() def on_selected_command_changed(self, *args): log.debug("on_selected_command_changed") self._update_buttons_according_to_selected_contact() self._update_text_in_textview() def _update_buttons_according_to_selected_contact(self): command = self._get_currently_selected_command(False) if command is None: #TODO enable/disable buttons self.stop_command_button_widget.set_sensitive(False) self.start_command_button_widget.set_sensitive(False) self.openlog_button_widget.set_sensitive(False) else: # log.debug("command: %s" % (command.identifier)) if command.get_state_info() in [STATE_STARTING, STATE_RUNNING, STATE_STOPPING]: self.stop_command_button_widget.set_sensitive(True) self.start_command_button_widget.set_sensitive(False) self.openlog_button_widget.set_sensitive(True) elif command.get_state_info() in [STATE_STOPPED, STATE_NOSLAVE, INFO_DONE, INFO_FAILED, INFO_TODO, INFO_GAVEUP]: self.stop_command_button_widget.set_sensitive(False) self.start_command_button_widget.set_sensitive(True) self.openlog_button_widget.set_sensitive(True) def _setup_treeview(self): """ Needs attributes; * self.tree_view_widget * self.model_sort """ # Set initial sorting column and order. sorting_column_number = 0 self.model_sort.set_sort_column_id(sorting_column_number, gtk.SORT_ASCENDING) NUM_COLUMNS = 5 columns = [None] * NUM_COLUMNS # Set column title columns[0] = gtk.TreeViewColumn(_("Identifier")) columns[1] = gtk.TreeViewColumn(_("Command")) columns[2] = gtk.TreeViewColumn(_("Host")) columns[3] = gtk.TreeViewColumn(_("Executions")) # How many times columns[4] = gtk.TreeViewColumn(_("State")) # str # Set default properties for each column cells = [None] * NUM_COLUMNS for i in range(NUM_COLUMNS): self.tree_view_widget.append_column(columns[i]) columns[i].set_expand(True) columns[i].set_max_width(400) columns[i].set_resizable(True) columns[i].set_sort_column_id(i) cells[i] = gtk.CellRendererText() cells[i].set_property("width-chars", 20) columns[i].pack_start(cells[i], False) #True) columns[i].set_attributes(cells[i], text=i) # Set some custom properties cells[0].set_property("width-chars", 14) # Identifier cells[1].set_property("width-chars", 20) # Command cells[2].set_property("width-chars", 12) # Host cells[3].set_property("width-chars", 8) # Executions cells[4].set_property("width-chars", 8) # State def _get_iter_for_command_row(self, looking_for): """ @param looking_for: identifier to look for @return: tree path - or None @rtype: gti.Iter """ list_store = self.model_sort.get_model() row_number = 0 found_it = False for row in iter(list_store): identifier = row[self.IDENTIFIER_COLUMN] if identifier == looking_for: found_it = True break row_number += 1 if found_it: return list_store.get_iter(row_number) else: return None def _add_command_in_tree(self, command): """ adds a row with the data of the command. """ # TODO: update it every time it changes. (the PID, etc) list_store = self.model_sort.get_model() list_store.append(self._format_command(command)) #self._set_tooltip_for_command(command) #self.commands[command.identifier] = command #print("Connecting state changed signal to GUI.") command.child_state_changed_signal.connect(self.on_command_status_changed) command.child_pid_changed_signal.connect(self.on_command_child_pid_changed) command.ssh_error_signal.connect(self.on_ssh_error) command.command_not_found_signal.connect(self.on_command_not_found) # # FIXME: did not get this to work yet # def _set_tooltip_for_command(self, command): # # trying to add a tooltip # tree_iter = self._get_iter_for_command_row(command.identifier) # tooltip = gtk.Tooltip() # txt = _("Command: %(command)s\n") % {"command": command.command} # tooltip.set_markup(txt) # log.debug("getting tree iter %s for command %s" % (tree_iter, command.identifier)) # tree_path = self.model_sort.get_model().get_path(tree_iter) # path in the list_store # log.debug("Adding tooltip %s in tree: %s" % (txt, tree_path)) # # (GtkTreeView *tree_view, GtkTooltip *tooltip, GtkTreePath *path); # self.tree_view_widget.set_tooltip_row(tooltip, tree_path) def _remove_command_from_tree(self, command): """ When a command is removed, removes it from the tree view. """ list_store = self.model_sort.get_model() looking_for = command.identifier #list_store.append(self._format_command(command)) row_number = 0 for row in iter(list_store): identifier = row[self.IDENTIFIER_COLUMN] if identifier == looking_for: # Delete it! break row_number += 1 log.debug("Removing a row from the list store.") list_store.remove(list_store.get_iter(row_number)) #log.debug("Removing GUI's slot for state changed signal.") command.child_state_changed_signal.disconnect(self.on_command_status_changed) command.child_pid_changed_signal.disconnect(self.on_command_child_pid_changed) command.ssh_error_signal.disconnect(self.on_ssh_error) def on_command_not_found(self, command, command_txt): log.debug("on_command_not_found %s" % (command)) error_message = "Command %s not found on host %s.\nThe command line is %s." % (command, command.host, command_txt) dialogs.ErrorDialog.create(error_message) def on_ssh_error(self, command, error_message): log.debug("on_ssh_error %s" % (command)) dialogs.ErrorDialog.create(error_message) def _update_row(self, command): list_store = self.model_sort.get_model() looking_for = command.identifier #log.debug "look for:", looking_for for row in iter(list_store): identifier = row[self.IDENTIFIER_COLUMN] if identifier == looking_for: #log.debug identifier, "MATCHES!!!!!!!!!" #TODO: update only columns how_many_times_run and child_state cells = self._format_command(command) for i in range(len(cells)): row[i] = cells[i] break #for v in row: # log.debug("updating row : %s" % (v)) def _format_command(self, command): """ Returns a list of values for the cells in the row of a command """ host = "localhost" if command.host is not None: host = command.host executions = command.how_many_times_run #state = command.child_state state = command.get_state_info() return [ command.identifier, command.command, host, executions, state ] def on_menu_open_logs(self, *args): #TODO: if hasattr(self.master, "log_dir"): open_path(self.master.log_dir) #log.debug("open logs") def on_menu_view_master_log(self, *args): tail_master_log(self.master) def _create_main_menu(self, window): ui_string = """ """ ag = gtk.ActionGroup('WindowActions') actions = [ ('FileMenu', None, '_File'), ('OpenLoggingDir', None, '_OpenLoggingDir', None, _('Open Logging Directory'), self.on_menu_open_logs), ('OpenMasterLog', None, '_OpenMasterLog', None, _('Open the master log file'), self.on_menu_view_master_log), ('Quit', gtk.STOCK_QUIT, '_Quit', 'Q', _('Quit'), self.destroy_app), ('HelpMenu', None, '_Help'), ('About', gtk.STOCK_ABOUT, '_About', None, _('About the application'), self.on_about), ('Manual', gtk.STOCK_HELP, '_Manual', None, _('Manual'), self.on_man_page), ] ag.add_actions(actions) self.ui_manager = gtk.UIManager() self.ui_manager.insert_action_group(ag, 0) self.ui_manager.add_ui_from_string(ui_string) window.add_accel_group(self.ui_manager.get_accel_group()) return self.ui_manager.get_widget('/Menubar') def on_about(self, *args): #print "on about" self.show_about_dialog() def show_about_dialog(self): About().show_about_dialog() def on_man_page(self, *args): #print "on man page menu item chosen" man_lunch() def _get_currently_selected_command(self, show_error_if_none=True): """ Returns a lunch.commands.Command object or None. """ ret = None selection = self.tree_view_widget.get_selection() model, rows = selection.get_selected() if rows is None: ret = None if show_error_if_none: msg = _("Please select a process in the list.") d = defer.Deferred() dialog = dialogs.ErrorDialog(d, msg) else: # log.debug('getting the currently selected row in the tree view.') row = rows # only one row selected at a time in this version identifier = model.get_value(row, self.IDENTIFIER_COLUMN) # log.debug('id %s' % (identifier)) ret = self.master.commands[identifier] return ret def on_openlog_clicked(self, widget): command = self._get_currently_selected_command() if command is not None: tail_child_log(command) def on_stop_command_clicked(self, widget): command = self._get_currently_selected_command() if command is not None: command.stop() def on_start_command_clicked(self, widget): command = self._get_currently_selected_command() if command is not None: command.start() def on_command_status_changed(self, command, new_state): """ Called when the child_state_changed_signal of the command is triggered. @param command L{lunch.commands.Command} @param new_state str """ log.debug("lunch-child %s> Its state changed to %s" % (command.identifier, new_state)) self._update_command(command) def on_command_child_pid_changed(self, command, new_pid): """ Called when the child_pid_changed_signal of the command is triggered. @param command L{lunch.commands.Command} @param new_pid int """ log.debug("lunch-child %s> Its PID changed to %s" % (command.identifier, new_pid)) self._update_command(command) def _update_command(self, command): self._update_row(command) self._update_buttons_according_to_selected_contact() self._update_text_in_textview_if_command_is_selected(command) def destroy_app(self, widget, data=None): """ Destroy method causes application to exit when main window closed If you return FALSE in the "delete_event" signal handler, GTK will emit the "destroy" signal. Returning TRUE means you don't want the window to be destroyed. This is useful for popping up 'are you sure you want to quit?' type dialogs. """ return self.confirm_and_quit() def confirm_and_quit(self): """ If needed, ask the user if he really wants to quit, and then quit if the answer is yes. @rettype: bool """ still_some_running = False for c in self.master._get_all(): if c.child_state == STATE_RUNNING: still_some_running = True def _cb(result): if result: log.info("Destroying the Lunch window.") if reactor.running: log.info("reactor.stop()") reactor.stop() else: log.info("Not quitting.") if self.confirm_close and still_some_running: d = dialogs.YesNoDialog.create(_("Really quit ?\nAll launched processes will quit as well.")) d.addCallback(_cb) return True else: _cb(True) return False def start_gui(lunch_master): """ Starts the GTK GUI :rettype: L{LunchApp} """ log.info("Starting the GUI.") app = LunchApp(lunch_master) #self.slave_state_changed_signal = sig.Signal() return app lunch-0.4.0/lunch/sig.py0000644000000000000000000000416411437735014013601 0ustar rootroot#!/usr/bin/env python """ File: signal.py Author: Thiago Marcos P. Santos Created: August 28, 2008 Purpose: A signal/slot implementation Source: http://code.activestate.com/recipes/576477/ A Signal calls all the callbacks registered in its slots whenever it state changes. """ from weakref import WeakValueDictionary class Signal(object): """ A Signal is callable. When called, it calls all the callables in its slots. """ def __init__(self): self._slots = WeakValueDictionary() def __call__(self, *args, **kargs): for key in self._slots: func, _ = key func(self._slots[key], *args, **kargs) def connect(self, slot): """ Slots must call this to register a callback method. :param slot: callable """ key = (slot.im_func, id(slot.im_self)) self._slots[key] = slot.im_self def disconnect(self, slot): """ They can also unregister their callbacks here. :param slot: callable """ key = (slot.im_func, id(slot.im_self)) if key in self._slots: self._slots.pop(key) def clear(self): """ Clears all slots """ self._slots.clear() if __name__ == "__main__": # Sample usage: class Model(object): def __init__(self, value): self._value = value self.changed = Signal() def set_value(self, value): self._value = value self.changed() # Emit signal def get_value(self): return self._value class View(object): def __init__(self, model): self.model = model model.changed.connect(self.model_changed) def model_changed(self): print("New value: %s" % (self.model.get_value())) model = Model(10) view1 = View(model) view2 = View(model) view3 = View(model) model.set_value(20) del view1 # remove one listener model.set_value(30) model.changed.clear() # remove all listeners model.set_value(40) # To disconnect : # model.changed.disconnect(view1.model_changed) lunch-0.4.0/lunch/states.py0000644000000000000000000000220211437735014014311 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ Constants for the states of the slave processes """ STATE_STARTING = "STARTING" STATE_RUNNING = "RUNNING" STATE_STOPPING = "STOPPING" STATE_STOPPED = "STOPPED" STATE_NOSLAVE = "NOSLAVE" # for master only # these two are not states per se, but returned by the get_state_info() method INFO_DONE = "DONE" INFO_FAILED = "FAILED" INFO_TODO = "TODO" INFO_GAVEUP = "GAVE UP" lunch-0.4.0/lunch/runner.py0000644000000000000000000001422611437735014014330 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ Main entry point of the lunch master application. """ import os import sys import traceback from optparse import OptionParser from lunch import __version__ DESCRIPTION = "Lunch is a distributed process launcher for GNU/Linux. The Lunch master launches lunch-slave processes through an encrypted SSH session if on a remote host. Those slave processes can in turn launch the desired commands on-demand." def run(): """ Runs the application. """ parser = OptionParser(usage="%prog [config file] [options]", version="%prog " + __version__, description=DESCRIPTION) parser.add_option("-f", "--config-file", type="string", help="Specifies the python config file. You can also simply specify the config file as the first argument.") parser.add_option("-l", "--logging-directory", type="string", default="/var/tmp/lunch", help="Specifies the logging and pidfile directory for the master. Default is /var/tmp/lunch") parser.add_option("-q", "--log-to-file", action="store_true", help="Enables logging master infos to file and disables logging to standard output.") parser.add_option("-g", "--graphical", action="store_true", help="Enables the graphical user interface.") parser.add_option("-v", "--verbose", action="store_true", help="Makes the logging output verbose.") parser.add_option("-d", "--debug", action="store_true", help="Makes the logging output very verbose.") parser.add_option("-k", "--kill", action="store_true", help="Kills another lunch master that uses the same config file and logging directory. Exits once it's done.") (options, args) = parser.parse_args() # --------- set configuration file if options.config_file: config_file = options.config_file DEFAULT_CONFIG_FILE = os.path.expanduser("~/.lunchrc") if len(args) == 1 and not options.config_file: #log.msg("DEBUG: overriding config_file with %s" % (args[0])) config_file = args[0] else: config_file = DEFAULT_CONFIG_FILE # --------- set if logging if options.log_to_file: file_logging_enabled = True else: file_logging_enabled = False logging_dir = options.logging_directory # ---------- load the right reactor if options.graphical: try: from twisted.internet import gtk2reactor gtk2reactor.install() # has to be done before importing reactor import gtk # just for a test GUI_ENABLED = True #print("Successfully loaded the GTK+ graphical user interface.") except ImportError, e: print("Could not load the GTK+ graphical user interface. " + str(e)) GUI_ENABLED = False else: # print("Using lunch master without the GUI.") GUI_ENABLED = False from twisted.internet import reactor from twisted.internet import defer # --------- load the module and run from lunch import master error_message = None if not os.path.exists(config_file): error_message = "No such file: %s" % (config_file) else: log_level = 'warning' if options.verbose: log_level = 'info' if options.debug: log_level = 'debug' if options.kill: def _killed_cb(result): #TODO: show a dialog to the user if --graphical is given. if reactor.running: reactor.stop() master.start_stdout_logging(log_level=log_level) #FIXME: should be able to log to file too identifier = master.gen_id_from_config_file_name(config_file) master.log.info("Will check if lunch master %s is running and kill it if so." % (identifier)) deferred = master.kill_master_if_running(identifier=identifier, directory=logging_dir) deferred.addCallback(_killed_cb) reactor.run() sys.exit(0) try: #print("DEBUG: using config_file %s" % (config_file)) lunch_master = master.run_master(config_file, log_to_file=file_logging_enabled, log_dir=logging_dir, log_level=log_level) except master.FileNotFoundError, e: #print("Error starting lunch as master.") msg = "A configuration file is missing. Try the --help flag. " msg += str(e) error_message = msg #sys.exit(1) except RuntimeError, e: #print(str(e)) error_message = str(e) #sys.exit(1) except Exception, e: error_message = "There is an error in your lunch file !\n" error_message += traceback.format_exc() if error_message is not None: print(error_message) if GUI_ENABLED: from lunch import dialogs def _cb(result): # stops reactor when the error dialog is closed reactor.stop() d = defer.Deferred() d.addCallback(_cb) error_dialog = dialogs.ErrorDialog(d, error_message) print("Running reactor to show error dialog.") reactor.run() # need it for the GTK error dialog print("Reactor stopped. Exiting.") sys.exit(1) if GUI_ENABLED: from lunch import gui app = gui.start_gui(lunch_master) #print("Done starting the app.") try: reactor.run() except KeyboardInterrupt: #log.msg("Ctrl-C in Master.", logging.INFO) #lunch_master.quit_master() reactor.stop() lunch-0.4.0/lunch/commands.py0000644000000000000000000010222711437735014014617 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ A L{lunch.commands.Command} is the interface to what command line the user wants to starts. Once given to a L{lunch.master.Master}, each command starts a lunch-slave process, communicating with it via its stdin and stdout. The lunch-slave is then told which command to launch and asked to launch it, and stop it, on-demand. Author: Alexandre Quessy """ import os import stat import time import logging import warnings from twisted.internet import defer from twisted.internet import error from twisted.internet import protocol from twisted.internet import reactor from twisted.internet import task from twisted.internet import utils from twisted.python import failure #from twisted.python import log from twisted.python import logfile from twisted.python import procutils from lunch import sig from lunch import graph from lunch.states import * from lunch import logger log = logger.start(name='commands') def run_and_wait(executable, *arguments): """ Runs a command and trigger its deferred with the output when done. Returns a deferred. """ # TODO: use it. try: executable = procutils.which(executable)[0] except IndexError: msg = "Could not find executable %s" % (executable) return failure.Failure(RuntimeError(msg)) d = utils.getProcessOutput(executable, arguments) def cb(result, executable, arguments): print 'Call to %s %s returned.\nResult: %s\n' % (executable, arguments, result) return result def eb(reason, executable, arguments): print 'Calling %s %s failed.\nError: %s' % (executable, arguments, reason) return reason d.addCallback(cb, executable, list(arguments)) d.addErrback(eb, executable, list(arguments)) return d class SlaveProcessProtocol(protocol.ProcessProtocol): """ Process of a lunch-slave. (through SSH, or directly bash) The Lunch Master controls it by its stdin and monitors it with its stdout. """ def __init__(self, command): """ @param command: L{Command} instance. """ self.command = command def connectionMade(self): """ Called once the process is started. """ self.command._on_connection_made() def outReceived(self, data): """ Called when text is received from the lunch-slave process stdout Twisted will not splitlines, it gives an arbitrary amount of data at a time. This way, our manager only gets one line at a time. """ for line in data.splitlines(): if line != "": self.command._received_message(line) def errReceived(self, data): """ Called when text is received from the lunch-slave process stderr """ for line in data.splitlines().strip(): if line != "": log.debug("stderr: " + line + "\n") def processEnded(self, reason): """ Called when the lunch-slave process has exited. status is probably a twisted.internet.error.ProcessTerminated "A process has ended with a probable error condition: process ended by signal 1" This is called when all the file descriptors associated with the child process have been closed and the process has been reaped. This means it is the last callback which will be made onto a ProcessProtocol. The status parameter has the same meaning as it does for processExited. """ exit_code = reason.value.exitCode log.info("Slave %s process ended with %s." % (self.command.identifier, exit_code)) self.command._on_process_ended(reason.value.exitCode) def inConnectionLost(self): pass #log.msg("Slave stdin has closed.") def outConnectionLost(self): pass #log.msg("Slave stdout has closed.") def errConnectionLost(self): pass #log.msg("Slave stderr has closed.") def processExited(self, reason): """ This is called when the lunch-slave process has been reaped, and receives information about the process' exit status. The status is passed in the form of a Failure instance, created with a .value that either holds a ProcessDone object if the process terminated normally (it died of natural causes instead of receiving a signal, and if the exit code was 0), or a ProcessTerminated object (with an .exitCode attribute) if something went wrong. """ exit_code = reason.value.exitCode log.info("process has exited : %s." % (str(exit_code))) class Command(object): """ Manages a lunch-slave process, telling it which child process to start. Uses a SlaveProcessProtocol, which in turn controls a lunch-slave process. This is also what the programmer uses to control a child process. """ #TODO: add gid #TODO: allow to switch uid (when running on localhost) #TODO: add delay_kill (to the add_command "private" function) #TODO: add clear_old_logs #TODO: add time_started #TODO: add enabled. (for respawning or not a process, without changing its respawn attribute. #TODO: move send_* and recv_* methods to the SlaveProcessProtocol. #TODO: add wait_returned attribute. (commands after which we should wait them to end before calling next) def __init__(self, command=None, identifier=None, env=None, user=None, host=None, order=None, sleep_after=0.25, respawn=True, minimum_lifetime_to_respawn=0.5, log_dir=None, depends=None, verbose=False, try_again_delay=0.25, give_up_after=0, enabled=None, delay_before_kill=8.0, ssh_port=None): """ @param command: Shell string. The first item is the name of the name of the executable. @param depends: Commands to which this command depends on. List of strings. @param enabled: Whether it's enabled or not. @param env: dict with environment variables to set for the process to run. @param host: Host name or IP address, if spawned over SSH. @param identifier: Any string. Used as a file name, so avoid spaces and exotic characters. @param log_dir: Full path to the directory to save log files in. @param minimum_lifetime_to_respawn: Minimum time a process must have lasted to be respawned. @param respawn: Set to False if this is a command that must be ran only once. @param sleep_after: How long to wait before launching next command after this one. @param delay_before_kill: Time to wait between sending SIGTERM and SIGKILL signals when it's time to stop the child process. @param user: User name, if spawned over SSH. @param verbose: Prints more information if set to True. @param ssh_port: SSH port to use. @type command: str @type depends: list @type enabled: bool @type env: dict @type host: str @type identifier: str @type log_dir: str @type minimum_lifetime_to_respawn: float @type respawn: bool @type sleep_after: float @type user: str @type verbose: bool @type delay_before_kill: float @type ssh_port: int @param try_again_delay: Time to wait before trying again if it crashes at startup. @type try_again_delay: C{float} @param give_up_after: How many times to try again before giving up. @type give_up_after: C{int} """ self.command = command self.identifier = identifier self.env = {} if env is not None: self.env.update(env) self.user = user self.host = host self.ssh_port = ssh_port self.order = order self.sleep_after = sleep_after self.respawn = respawn self.enabled = True if enabled is not None: self.enabled = enabled self.to_be_deleted = False self.depends = depends self.how_many_times_run = 0 self.how_many_times_tried = 0 self.delay_before_kill = delay_before_kill self.verbose = verbose self.retval = 0 self.gave_up = False self.number_of_lines_received_from_slave = 0 self._has_shown_ssh_error = False self._has_shown_notfound_error = False self.try_again_delay = try_again_delay self._current_try_again_delay = try_again_delay # doubles up each time we try self._next_try_time = 0 self._received_ready = False self._previous_launching_time = 0 self.give_up_after = give_up_after # 0 means infinity of times self.minimum_lifetime_to_respawn = minimum_lifetime_to_respawn #FIXME: rename self._quit_slave_deferred = None if log_dir is None: log_dir = "/var/tmp/lunch"# XXX Overriding the child's log dir. # XXX: used to be something like: #SLAVE_LOG_SUBDIR = "lunch_log" #slave_log_dir = os.path.join(os.getcwd(), SLAVE_LOG_SUBDIR) self.child_log_dir = log_dir # for both lunch-slave and child. If not set, defaults to $PWD/lunch_log self.slave_log_dir = self.child_log_dir # ------- private attributes: self.slave_state = STATE_STOPPED # state of the lunch-slave process, not the child process that the lunch-slave handles self.child_state = STATE_STOPPED # state of the child process that the lunch-slave handles. self.slave_state_changed_signal = sig.Signal() # params: self, new_state self.child_state_changed_signal = sig.Signal() # params: self, new_state self.child_pid_changed_signal = sig.Signal() # params: self, new_pid self.command_not_found_signal = sig.Signal() # params: self, command self.ssh_error_signal = sig.Signal() # params: self, error_message if command is None: raise RuntimeError("You must provide a command to be run.") log.info("Creating command %s ($ %s) on %s@%s" % (self.identifier, self.command, self.user, self.host)) #self.send_stop() self._process_protocol = None self._process_transport = None # Some attributes might be changed by the master, namely identifier and host. # That's why we sait until start() is called to initiate the slave_logger. self.slave_logger = None self.child_pid = None def is_ready_to_be_started(self): # self.enabled ret = self._next_try_time <= time.time() and self.child_state == STATE_STOPPED if ret and self.slave_state == STATE_RUNNING: if not self._received_ready: # log.debug("Not ready to start child %s since we did not receive the ready message." % (self)) ret = False return ret def _start_logger(self): """ Creates a log file for the lunch-slave's stdout. """ if self.slave_logger is None: # the lunch-slave log file slave_log_file = "slave-%s.log" % (self.identifier) if not os.path.exists(self.slave_log_dir): try: os.makedirs(self.slave_log_dir) except OSError, e: raise RuntimeError("You need to be able to write in the current working directory in order to write log files. %s" % (e)) self.slave_logger = logfile.LogFile(slave_log_file, self.slave_log_dir) def start(self): """ Starts the lunch-slave process and its child if they are not running. If the lunch-slave is already started, starts its child. """ self.enabled = True #FIXME:2010-08-17:aalex:We won't reset the _has_shown_ssh_error state when starting, otherwise it shows the error many times. # self._has_shown_ssh_error = False self.gave_up = False if self.how_many_times_tried == 0: self._current_try_again_delay = self.try_again_delay self.how_many_times_tried += 1 self._start_logger() # If the lunch-slave is already running, we only need to tell it to start the child if self.child_state == STATE_RUNNING: self.log("%s: Child is already running." % (self.identifier)) return if self.slave_state == STATE_RUNNING and self.child_state == STATE_STOPPED: self._send_all_startup_commands() return elif self.child_state in [STATE_STOPPING, STATE_STARTING]: self.log("Cannot start child %s that is %s." % (self.identifier, self.child_state)) return else: if self.slave_state in [STATE_STARTING, STATE_STOPPING]: self.log("Cannot start a lunch-slave %s that is %s." % (self.identifier, self.slave_state)) return # XXX else: # lunch-slave is STOPPED # --------------- start the lunch-slave, and then its child self.number_of_lines_received_from_slave = 0 self._received_ready = False if self.host is None: # if self.user is not None: # TODO: Set gid if user is not None... is_remote = False # not using SSH _command = ["lunch-slave", "--id", self.identifier] else: self.log("We will use SSH since host is %s" % (self.host)) is_remote = True # using SSH _command = ["ssh"] if self.ssh_port is not None: _command.extend(["-p", str(self.ssh_port)]) if self.user is not None: _command.extend(["-l", self.user]) _command.extend([self.host]) _command.extend(["lunch-slave", "--id", self.identifier]) # I hope you put your SSH key on the remote host ! # FIXME: we should pop-up a terminal if keys are not set up. try: _command[0] = procutils.which(_command[0])[0] except IndexError: raise RuntimeError("Could not find path of executable %s." % (_command[0])) log.info("lunch-slave %s> $ %s" % (self.identifier, " ".join(_command))) self._process_protocol = SlaveProcessProtocol(self) #try: proc_path = _command[0] args = _command environ = {} environ.update(os.environ) # passing the whole env (for SSH keys and more) self.set_slave_state(STATE_STARTING) self.log("Starting lunch-slave: %s" % (self.identifier)) self._previous_launching_time = time.time() self._process_transport = reactor.spawnProcess(self._process_protocol, proc_path, args, environ, usePTY=True) def _format_env(self): """ Format the environment variables to send them to the lunch-slave as a series of key-value pairs. """ txt = "" for k, v in self.env.iteritems(): txt += "%s=%s " % (k, v) return txt def _on_connection_made(self): if self.slave_state == STATE_STARTING: self.set_slave_state(STATE_RUNNING) else: msg = "Connection made with lunch-slave %s, even if not expecting it." % (self.identifier) self.log(msg, logging.ERROR) def send_do(self): """ Send to the lunch-slave the command line to launch its child. """ self.send_message("do", self.command) def send_ping(self): self.send_message("ping") # for fun def send_run(self): self.send_message("run") self.log("lunch-child %s> $ %s" % (self.identifier, self.command), logging.INFO) def send_env(self): self.send_message("env", self._format_env()) def send_logdir(self): self.send_message("logdir", self.child_log_dir) def send_message(self, key, data=""): """ Sends a command to the lunch-slave. @param key: string @param data: string """ msg = "%s %s\n" % (key, data) if self.verbose: self.log("lunch-slave %s> Sending %s" % (self.identifier, msg.strip())) self._process_transport.write(msg) def __del__(self): #TODO: send "stop" and SIGKILL if the lunch-slave and the child processes are stil running. if self.slave_logger is not None: self.slave_logger.close() def _looks_like_ssh_error(self, line): """ Checks the string received to see if it looks like a SSH error message. @return: An error message if there is a problem, or None otherwise. @rtype: C{str} """ #XXX: if is returns a string, the master will give it up ret = None # log.debug("Checking if it looks like a SSH error: %s" % (line)) if "password:" in line: ret = "The SSH server asks for a password. Make sure you use the right user name and that your public SSH key is installed on the remote host %s." % (self.host) #giving up elif "Enter passphrase for key" in line: ret = "The SSH client asks for a passphrase to unlock your local private SSH key for which you have the corresponding public key on host %s. You should avoid this to be asked by providing that passphrase a first time using SSH by hand." % (self.host) #giving up elif "Connection refused" in line: port = 22 if self.ssh_port is not None: port = self.ssh_port ret = "The SSH server is not running on port %d of host %s or not available." % (port, self.host) #TODO: try to reconnect elif "No route to host" in line: ret = "We cannot find host %s." % (self.host) #TODO: try to reconnect elif "command not found" in line: ret = "The lunch-slave command is not installed on the host %s." % (self.host) #giving up elif "ssh_exchange_identification" in line: #FIXME: what is that? ret = "Some SSH problem occurred exchanging the identification on host %s. Is your host blacklisted?" % (self.host) elif "Could not resolve hostname" in line: ret = "Could not resolve hostname %s." % (self.host) if ret is not None: ret += "\nThe line received from SSH is :\n" + line ret += "\nThis error happend when trying to launch %s" % (self) log.error(line) log.error(ret) return ret def _received_message(self, line): """ Received one line of text from the lunch-slave through its stdout. """ #self.log("%8s: %s" % (self.identifier, line)) # FIXME: right now, we check all the output from that guy if True: #self.number_of_lines_received_from_slave == 0: ssh_error = self._looks_like_ssh_error(line) if ssh_error is not None: # It's a str log.error("--------- SSH PROBLEM: " + ssh_error + " -----------") # FIXME: self.enabled = False if not self._has_shown_ssh_error: self._has_shown_ssh_error = True self.ssh_error_signal(self, ssh_error) return #TODO: handle this self.number_of_lines_received_from_slave += 1 try: words = line.split(" ") key = words[0] mess = line[len(key) + 1:] except IndexError, e: #self.log("Index error parsing message from lunch-slave. %s" % (e), logging.ERROR) self.log('IndexError From lunch-slave %s: %s' % (self.identifier, line), logging.ERROR) else: # Dispatch the command to the appropriate method. Note that all you # need to do to implement a new command is add another do_* method. if key in ["do", "env", "run", "logdir", "stop"]: # FIXME: receiving in stdin what we send to stdin lunch-slave !!! pass #warnings.warn("We receive from the lunch-slave's stdout what we send to its stdin !") else: try: method = getattr(self, 'recv_' + key) except AttributeError, e: self.log('AttributeError: Parsing a line from lunch-slave %s: %s' % (self.identifier, line), logging.ERROR) #self.log(line) else: method(mess) def recv_ok(self, mess): """ Callback for the "ok" message from the lunch-slave. """ pass def recv_not_found(self, mess): """ Callback for the "not_found" message from the lunch-slave. That's when bash complains that it didn't find the command we are trying to run. """ log.error("lunch-slave %s> Command not found: %s" % (self, self.command)) if not self._has_shown_notfound_error: self._has_shown_notfound_error = True self.command_not_found_signal(self, self.command) def recv_child_pid(self, mess): """ Callback for the "child_pid" message from the lunch-slave. The arg is the child's PID """ self.log("lunch-slave %s> child_pid %s" % (self.identifier, mess)) words = mess.split(" ") self.child_pid = int(words[0]) self.log("%s: PID of child is %s" % (self.identifier, self.child_pid), logging.INFO) self.child_state_changed_signal(self, self.child_pid) def recv_msg(self, mess): """ Callback for the "msg" message from the lunch-slave. """ pass def recv_retval(self, mess): """ Callback for the "retval" message from the lunch-slave. """ self.log("lunch-slave %s> retval %s" % (self.identifier, mess)) words = mess.split(" ") self.retval = int(words[0]) self.log("%s: Return value of child is %s" % (self.identifier, self.retval), logging.INFO) def recv_log(self, mess): """ Callback for the "log" message from the lunch-slave. """ self.log("lunch-slave %s> log %s" % (self.identifier, mess)) def recv_error(self, mess): """ Callback for the "error" message from the lunch-slave. """ self.log("lunch-slave %s> error %s" % (self.identifier, mess), logging.ERROR) def recv_pong(self, mess): """ Callback for the "pong" message from the lunch-slave. """ pass #self.log("pong from %s" % (self.identifier)) def recv_bye(self, mess): """ Callback for the "bye" message from the lunch-slave. """ self.log("lunch-slave %s> %s" % (self.identifier, "BYE (slave quits)"), logging.ERROR) def get_state_info(self): """ Returns a high-level comprehensive state for the user to see in the GUI. """ #log.debug("gave up: %s" % (self.gave_up)) if self.child_state == STATE_STOPPED: if self.how_many_times_run == 0: return INFO_TODO elif self.gave_up: return INFO_GAVEUP elif not self.respawn: return INFO_DONE elif not self.enabled: return STATE_STOPPED elif self.retval != 0: return INFO_FAILED else: return STATE_STOPPED # INFO_FAILED? else: return self.child_state def _give_up_if_we_should(self): """ Check if we should give up and give up if so. """ # double the time to wait before trying again. # self.wait_before_trying_again -- this one never changes # self._wait_before_trying_again_next_time -- this one is doubled each time. if self.give_up_after != 0 and self.how_many_times_tried > self.give_up_after: self.gave_up = True self.enabled = False log.info("Gave up restarting command %s" % (self.identifier)) else: self._next_try_time = time.time() + self._current_try_again_delay log.info("%s: Will wait %f seconds before trying again." % (self.identifier, self._current_try_again_delay)) self._current_try_again_delay *= 2 self.how_many_times_tried += 1 def recv_state(self, mess): """ Callback for the "state" message from the child. Received child state. """ words = mess.split(" ") previous_state = self.child_state new_state = words[0] #print("%s's child state: %s" % (self.identifier, new_state)) self.log("lunch-child %s> Its state changed to %s" % (self.identifier, new_state)) if new_state == STATE_STOPPED and self.enabled and self.respawn: child_running_time = float(words[1]) if child_running_time < self.minimum_lifetime_to_respawn: self.log("lunch-child %s> Its running time of %s has been shorter than its minimum of %s." % (self.identifier, child_running_time, self.minimum_lifetime_to_respawn)) self._give_up_if_we_should() #else: # self._send_all_startup_commands() elif new_state == STATE_RUNNING: self.log("lunch-child %s> is running." % (self.identifier)) self._set_child_state(new_state) # IMPORTANT ! def recv_ready(self, mess): """ Callback for the "ready" message from the lunch-slave. The lunch-slave sends that to the master when launched. It means it is ready to received commands. """ self._received_ready = True if self.enabled: self._send_all_startup_commands() def _send_all_startup_commands(self): """ Tells the lunch-slave to launch its child process. Sets up the environment and command so that the lunch-slave can launch the child. """ self.send_do() self.send_logdir() self.send_env() #self.send_ping() self.send_run() def _set_child_state(self, new_state): """ Called when it is time to change the state of the child process. """ if new_state == STATE_STOPPED: self.child_pid = None if self.child_state != new_state: if new_state == STATE_RUNNING: self.how_many_times_run += 1 self.child_state = new_state # log.msg(" --------------- XXX Trigerring signal %s" % (self.child_state)) self.child_state_changed_signal(self, self.child_state) def reset(self): """ Do not give up anymore and reset the trials thing. """ self.how_many_times_tried += 1 self.gave_up = False self._next_try_time = 0 self._current_try_again_delay = self.try_again_delay def stop(self): """ Tells the lunch-slave to stop its child. """ self.reset() self.enabled = False if self.child_state in [STATE_RUNNING, STATE_STARTING]: self.log('%s: stop' % (self.identifier), logging.INFO) self.send_stop() else: msg = "Cannot stop child %s that is %s." % (self.identifier, self.child_state) self.log(msg, logging.WARNING) def send_stop(self): self.send_message("stop") def quit_slave(self): """ Stops the lunch-slave process by sending it SIGTERM. (15) If called for a second time, sends it SIGKILL (9). One should first make sure the child process has quit before doing this, otherwise they are likely to become zombie processes, with no parent. @rtype: L{twisted.internet.defer.Deferred} """ DELAY_BETWEEN_EACH_SIGNAL = self.delay_before_kill if self._quit_slave_deferred is not None: raise RuntimeError("Slave seems to be already quitting.") self._quit_slave_deferred = defer.Deferred() _sigkill_delayed_call = None def _on_ended(result): # cancels the call fo _cl_sigkill if the lunch-slave died. if _sigkill_delayed_call is not None: if _sigkill_delayed_call.active(): _sigkill_delayed_call.cancel() if not self._quit_slave_deferred.called: self._quit_slave_deferred.callback(None) return result def _cl_sigterm(): # sends a sigterm # and later a sigkill self._process_transport.signalProcess(15) # signal.SIGTERM self.set_slave_state(STATE_STOPPING) self.log('Will stop lunch-slave %s.' % (self.identifier)) _sigkill_delayed_call = reactor.callLater(DELAY_BETWEEN_EACH_SIGNAL, _cl_sigkill) # --------------------------------------- def _cl_sigkill(): # sends sigkill if the lunch-slave is still running if self.slave_state == STATE_STOPPING: self.log("kill -9 Slave %s" % (self.identifier)) self._process_transport.signalProcess(9) # signal.SIGKILL # --------------------------------------- self._quit_slave_deferred.addCallback(_on_ended) # XXX: could also be done with a signal/slot if self.slave_state == STATE_STOPPED: self.log("The lunch-slave process %s is already in \"%s\" state." % (self.identifier, self.slave_state), logging.WARNING) self._quit_slave_deferred.callback(None) else: if self.slave_state in [STATE_RUNNING, STATE_STARTING]: if self.child_state in [STATE_RUNNING, STATE_STARTING]: self.stop() # self.send_stop() reactor.callLater(DELAY_BETWEEN_EACH_SIGNAL, _cl_sigterm) elif self.child_state == STATE_STOPPED: _cl_sigterm() elif self.slave_state == STATE_STOPPING: # second time this is called, force-quitting: self._process_transport.signalProcess(9) # signal.SIGKILL _cl_sigkill() return self._quit_slave_deferred def _on_process_ended(self, exit_code): """ The lunch-slave died ! Its child is probably dead too. (otherwise, it's a zombie with no parent) """ #TODO: add a signal slot for this event? #self.log("Exit code: " % (exit_code)) former_slave_state = self.slave_state if former_slave_state == STATE_STARTING: self.log("Slave %s died during startup." % (self.identifier), logging.ERROR) elif former_slave_state == STATE_RUNNING: if exit_code == 0: self.log("Slave %s exited." % (self.identifier)) else: self.log('Slave %s exited with error %s.' % (self.identifier, exit_code)) elif former_slave_state == STATE_STOPPING: self.log('Slave exited as expected.') self.set_slave_state(STATE_STOPPED) self._process_transport.loseConnection() #if self.respawn and self.enabled: #No! The master will take care of that. # self.log("Restarting the lunch-slave %s." % (self.identifier), logging.INFO) # self.start() if self._quit_slave_deferred is not None: self._quit_slave_deferred.callback(None) def log(self, msg, level=logging.DEBUG): """ Logs both to the lunch-slave's log file, and to the main app log. """ if self.slave_logger is not None: prefix = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) self.slave_logger.write("%s %s\n" % (prefix, msg)) self.slave_logger.flush() log_number_to_name = { logging.DEBUG: 'debug', logging.INFO: 'info', logging.WARNING: 'warning', logging.ERROR: 'error', logging.CRITICAL: 'critical', } if level == logging.DEBUG: log.debug(msg) elif level == logging.INFO: log.info(msg) elif level == logging.WARNING: log.warning(msg) elif level == logging.ERROR: log.error(msg) elif level == logging.CRITICAL: log.critical(msg) #log.msg(msg, logLevel=level) def set_slave_state(self, new_state): """ Trigger the slave_state_changed_signal when the state of the lunch-slave process changes. """ msg = "Slave %s is %s." % (self.identifier, new_state) self.log(msg) if self.slave_state != new_state: self.slave_state = new_state self.slave_state_changed_signal(self.slave_state) def __str__(self): return "%s" % (self.identifier) lunch-0.4.0/lunch/test/0000755000000000000000000000000011437735014013417 5ustar rootrootlunch-0.4.0/lunch/test/test_slave.py0000755000000000000000000000140011437735014016140 0ustar rootroot""" Test cases for the lunch-slave process. """ from twisted.trial import unittest class Test_Slave(unittest.TestCase): pass def test_ping(self): pass # def test_command(self): # pass # def test_env(self): # pass # def test_stdout_file(self): # pass # def test_run(self): # pass # def test_status(self): # pass # def test_stop(self): # pass # def test_help(self): # pass # def test_quit(self): # pass test_ping.skip = "TODO" # test_command.skip = "TODO" # test_env.skip = "TODO" # test_stdout_file.skip = "TODO" # test_run.skip = "TODO" # test_status.skip = "TODO" # test_stop.skip = "TODO" # test_help.skip = "TODO" # test_quit.skip = "TODO" lunch-0.4.0/lunch/test/__init__.py0000755000000000000000000000000011437735014015521 0ustar rootrootlunch-0.4.0/lunch/test/test_common.py0000755000000000000000000001070311437735014016324 0ustar rootroot""" Tests for lunch Master """ from twisted.trial import unittest from twisted.internet import defer from twisted.python import failure from twisted.internet import reactor from lunch import master from lunch import commands from lunch.states import * LOG_LEVEL = "warning" #LOG_LEVEL = "info" #LOG_LEVEL = "debug" master.start_stdout_logging(LOG_LEVEL) from lunch import logger log = logger.start(name="test") #TODO: add the path to lunch-slave to $PATH class Test_Master(unittest.TestCase): timeout = 4.0 # so that we don't wait in case of a problem def test_read_config(self): pass test_read_config.skip = "TODO." def test_add_remove_command(self): COMMAND_IDENTIFER = "test" COMMAND_LINE = "man man" _deferred = defer.Deferred() _master = master.Master() self.the_command = None def _later1(): # checks the the command has been added # removes the command self.the_command = _master.get_command(COMMAND_IDENTIFER) log.info("Set self.the_command to %s" % (self.the_command)) _master.remove_command(COMMAND_IDENTIFER) log.info("remove_command") reactor.callLater(0.1, _later2) def _later2(): log.info("_later2") # checks the the command has been removed if len(_master.get_all_commands()) != 0: msg = "The command did not get removed" log.info(msg) _deferred.errback(failure.Failure(failure.DefaultException(msg))) log.info("failed") else: log.info("removing the looping call") if _master._looping_call.running: d = _master._looping_call.deferred _master._looping_call.stop() # FIXME d.addCallback(_cb3) else: _deferred.callback(None) def _cb3(result): # Called when the looping call has been stopped log.info("quit all slaves") for command in _master.get_all_commands(): command.quit_slave() #TODO: use the Deferred if self.the_command.slave_state == STATE_RUNNING: self.the_command.quit_slave() #TODO: use the Deferred reactor.callLater(0.1, _later4) def _later4(): _deferred.callback(None) _master.add_command(commands.Command(COMMAND_LINE, identifier=COMMAND_IDENTIFER)) log.info("added command $ %s" % (COMMAND_LINE)) reactor.callLater(0.1, _later1) return _deferred test_add_remove_command.skip = "This test is still not working." #class Test_Command(unittest.TestCase): # def test_configure(self): # pass # def test_start(self): # pass # def test_stop(self): # pass # test_configure.skip = "TODO." # test_start.skip = "TODO." # test_stop.skip = "TODO." class Test_Master_Advanced(unittest.TestCase): timeout = 4.0 # so that we don't wait in case of a problem def setUp(self): self._master = master.Master() def test_add_commands_with_dependencies(self): COMMAND_A_IDENTIFIER = "test_A" COMMAND_B_IDENTIFIER = "test_B" COMMAND_C_IDENTIFIER = "test_C" COMMAND_LINE = "man man" DELAY = 0.1 self._deferred = defer.Deferred() self._master.add_command(commands.Command(COMMAND_LINE, identifier=COMMAND_A_IDENTIFIER)) self._master.add_command(commands.Command(COMMAND_LINE, identifier=COMMAND_B_IDENTIFIER, depends=[COMMAND_A_IDENTIFIER])) self._master.add_command(commands.Command(COMMAND_LINE, identifier=COMMAND_C_IDENTIFIER, depends=[COMMAND_B_IDENTIFIER])) log.info("added commands") def _cl1(): all = self._master.get_all_commands() self.failUnlessEqual(len(all), 3) _final_cleanup() def _final_cleanup(): log.info("_final_cleanup") d = _tear_down() def _tear_down_cb(result): self._deferred.callback(None) d.addCallback(_tear_down_cb) def _tear_down(): # return a deferred list log.info("_tear_down") # quit all slaves return self._master.cleanup() #_final_cleanup() _cl1() #reactor.callLater(DELAY, _cl1) return self._deferred lunch-0.4.0/lunch/test/test_graph.py0000644000000000000000000001122511437735014016132 0ustar rootroot""" Tests for the oriented ordred graph. (for process dependencies between each other) """ from twisted.trial import unittest from lunch import graph class Test_Graph(unittest.TestCase): def test_nodes(self): g = graph.DirectedGraph() root = g.get_root() g.add_node("a") a = g.get_supported_by(root) self.failUnlessEqual(a, ["a"]) # a should depend on root g.add_node("b", ["a"]) a2 = g.get_dependencies("b") self.failUnlessEqual(a, ["a"]) # b should depend on a g.add_node("c", ["a"]) #print(str(g)) li = g.get_supported_by("a") self.failUnlessEqual(li, ["b", "c"]) # b and c rely on a def test_clear(self): g = graph.DirectedGraph() g.add_node("x") g.add_node("y") g.add_node("z") root = g.get_root() li = g.get_supported_by(root) self.failUnlessEqual(li, ["x", "y", "z"]) li2 = g.get_all_nodes() self.failUnlessEqual(li2, [root, "x", "y", "z"]) g.clear() li3 = g.get_all_nodes() self.failUnlessEqual(li3, [root]) def test_traverse(self): g = graph.DirectedGraph() g.add_node("a") g.add_node("b", ["a"]) g.add_node("c", ["a"]) g.add_node("d", ["b"]) g.add_node("e", ["b"]) li = g.get_supported_by("a") self.failUnlessEqual(li, ["b", "c"]) li = g.get_supported_by("b") self.failUnlessEqual(li, ["d", "e"]) def test_remove_dep(self): g = graph.DirectedGraph() g.add_node("aaa") g.add_node("b", "aaa") li = g.get_supported_by("aaa") self.failUnlessEqual(li, ["b"]) g.remove_dependency("b", "aaa") li = g.get_supported_by("aaa") self.failUnlessEqual(li, []) li = g.get_dependencies("b") self.failUnlessEqual(li, [g.get_root()]) def test_detect_circularity(self): g = graph.DirectedGraph() g.add_node("a") g.add_node("b", ["a"]) g.add_node("c", ["b"]) # root <-- a <-- b <-- c if not g.depends_on("c", "a"): self.fail("Did not detect dependency.") #test_detect_circularity.skip = "Not ready yet." def test_detect_circularity_when_adding(self): g = graph.DirectedGraph() g.add_node("a") g.add_node("b", ["a"]) # root <-- a <-- b try: g.add_dependency("a", "b") except graph.GraphError, e: pass else: self.fail("Adding dep a->b should have raised an error.") g.add_node("c", ["b"]) # root <-- a <-- b <-- c try: g.add_dependency("a", "c") except graph.GraphError, e: pass else: self.fail("Adding dep a->c should have raised an error.") def test_get_all_dependees(self): g = graph.DirectedGraph() g.add_node("a") g.add_node("b", ["a"]) g.add_node("c", ["b"]) g.add_node("d", ["b"]) # root <-- a <-- b <-- c,d all = g.get_all_dependees("a") self.failUnlessEqual(all, ["b", "c", "d"]) class Test_Traversal(unittest.TestCase): """ Many tests using the same tree which contains all the cases. """ def setUp(self): self.g = graph.DirectedGraph() self.g.add_node("a") self.g.add_node("b", ["a"]) self.g.add_node("c", ["b"]) self.g.add_node("d", ["b"]) self.g.add_node("e") self.g.add_node("f", ["e"]) self.g.add_node("g") self.g.add_node("h") self.g.add_node("j") self.g.add_node("i", ['h', 'j']) # a -- b -- c # `-- d # e -- f # g # h -\ # j -- i def test_get_all_dependencies(self): li = self.g.get_all_dependencies("d") self.failUnlessEqual(li, ["b", "a"]) li = self.g.get_all_dependencies("i") self.failUnlessEqual(li, ["h", "j"]) li = self.g.get_all_dependencies("g") self.failUnlessEqual(li, []) def test_get_all_dependees(self): li = self.g.get_all_dependees("a") self.failUnlessEqual(li, ["b", "c", "d"]) li = self.g.get_all_dependees("e") self.failUnlessEqual(li, ["f"]) def test_iterator(self): iterator = graph.iter_from_root_to_leaves(self.g) visited = [] for n in iterator: visited.append(n) self.failUnlessEqual(visited, [self.g.ROOT, "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]) lunch-0.4.0/lunch/__init__.py0000644000000000000000000000010011437735014014540 0ustar rootroot"""Lunch: Distributed process launcher""" __version__ = "0.4.0" lunch-0.4.0/lunch/dialogs.py0000644000000000000000000001023111437735014014431 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ GTK Dialogs well integrated with Twisted. * Error dialog * Yes/No dialog """ if __name__ == "__main__": from twisted.internet import gtk2reactor gtk2reactor.install() # has to be done before importing reactor from twisted.internet import reactor from twisted.internet import defer import gtk class ErrorDialog(object): """ Error dialog. Fires the deferred given to it once done. """ def __init__(self, deferred, message): """ @param deferred: L{Deferred} @param message: str """ self.deferredResult = deferred parent = None error_dialog = gtk.MessageDialog( parent=None, flags=0, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=message) error_dialog.connect("close", self.on_close) error_dialog.connect("response", self.on_response) error_dialog.show() @staticmethod def create(message): """ Returns a Deferred which will be called with a True result. @param message: str @rettype: L{Deferred} """ d = defer.Deferred() dialog = ErrorDialog(d, message) return d def on_close(self, dialog, *params): pass #print("on_close %s %s" % (dialog, params)) def on_response(self, dialog, response_id, *params): #print("on_response %s %s %s" % (dialog, response_id, params)) if response_id == gtk.RESPONSE_DELETE_EVENT: pass #print("Deleted") elif response_id == gtk.RESPONSE_CANCEL: pass #print("Cancelled") elif response_id == gtk.RESPONSE_OK: pass # print("Accepted") self.terminate(dialog) def terminate(self, dialog): dialog.destroy() self.deferredResult.callback(True) class YesNoDialog(object): """ Yes/no confirmation dialog. Use the create static method as a factory. """ def __init__(self, deferred, message): self.deferredResult = deferred parent = None error_dialog = gtk.MessageDialog( parent=None, flags=0, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=message) error_dialog.set_modal(True) error_dialog.connect("close", self.on_close) error_dialog.connect("response", self.on_response) error_dialog.show() @staticmethod def create(message): """ Returns a Deferred which will be called with a boolean result. @param message: str @rettype: L{Deferred} """ d = defer.Deferred() dialog = YesNoDialog(d, message) return d def on_close(self, dialog, *params): pass # print("on_close %s %s" % (dialog, params)) def on_response(self, dialog, response_id, *params): pass # print("on_response %s %s %s" % (dialog, response_id, params)) if response_id == gtk.RESPONSE_DELETE_EVENT: pass # print("Deleted") self.terminate(dialog, False) elif response_id == gtk.RESPONSE_NO: pass # print("Cancelled") self.terminate(dialog, False) elif response_id == gtk.RESPONSE_YES: pass # print("Accepted") self.terminate(dialog, True) def terminate(self, dialog, answer): dialog.destroy() self.deferredResult.callback(answer) lunch-0.4.0/lunch/graph.py0000644000000000000000000001764711437735014014132 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ Module for handling dependencies between processes in a launching sequence. Example : sc needs jackd. We would start jackd first, then sc. If jackd crashes, we stop both, start jackd again, and then sc. """ class GraphError(Exception): """ Any error thrown by handling graph nodes. """ pass class DirectedGraph(object): """ Directed Graph with ordered edges between nodes. Useful for handling dependencies between processes. Some nodes can have two parents, but orphans are children of the root. Inspired from networkx.digraph.DiGraph """ ROOT = "__ROOT__" # the root node, to which all depend def __init__(self): self.deps = [] # dict node: list of [k, []] lists. self.deps.append([self.ROOT, []]) def clear(self): """ Removes all nodes and dependencies, except the root. """ self.deps = [[self.ROOT, []]] def add_node(self, node, deps=None): """ Adds a node to the graph, pointing to its dependencies. If no dependency is specified, it will point to the root. @param node: L{str} or L{str} The node to add. @param deps: :{list} of L{str} Its dependencies. Raises a GraphError if creating circular dependencies. """ if node not in self.get_all_nodes(): self.deps.append([node, []]) if deps is not None: if type(deps) is list: self.add_dependencies(node, deps) else: self.add_dependency(node, deps) else: self.add_dependency(node, self.ROOT) def add_dependencies(self, node_from, nodes_to): for d in nodes_to: self.add_dependency(node_from, d) def add_dependency(self, node_from, node_to): """ Adds a dependency between node_from and node_to. See networks.digraph.DiGraph.add_edge(node_from, node_to) @param node_from: str @param node_to: str """ # makes sure we don't already have this node dependencies = self.get_dependencies(node_from) if node_to not in dependencies: # prevent from circular dependencies if self.depends_on(node_to, node_from): raise GraphError("Circular dependency detected. A node cannot depend on itself.") else: dependencies.append(node_to) elif node_to not in self.deps: raise GraphError("The is no %s node in the dependencies graph." % (node_to)) def get_dependencies(self, node): """ Return a L{list} of nodes to which a node depends. @rettype list @param node: str """ for k, v in self.deps: if k is node: return v # else: raise GraphError("No node %s in graph." % (node)) def get_all_nodes(self): return [k for k, v in self.deps] def get_root(self): """ Returns the root of the graph. @rettype: L{str} """ return self.ROOT def remove_dependency(self, node_from, node_to): """ If no dependency if left, it will depend on the root. """ # might raise a ValueError dependencies = self.get_dependencies(node_from) if node_to in dependencies: dependencies.remove(node_to) if dependencies == []: dependencies.append(self.ROOT) else: raise GraphError("No dependency %s for node %s.", node_to, node_from) def remove_node(self, node): """ Removes a node from the graph. Removes all the dependencies of other nodes to this one. """ if node in self.get_all_nodes(): for k, v in self.deps: if k is node: self.deps.remove([k, v]) else: raise GraphError("No node %s in graph." % (node)) def get_supported_by(self, node=None): """ Returns the list of nodes that are directly supported by the given one. @param node: str or None. If None, will return the nodes supported by the root. """ if node is None: node = self.ROOT ret = [] for k, v in self.deps: if node in v: ret.append(k) return ret def get_all_dependees(self, node=None): """ Returns the list of all the nodes that are supported by the given one, recursively ! @param node: str or None. If None, will return the nodes supported by the root. """ if node is None: node = self.ROOT ret = [] for k, v in self.deps: if node in v: ret.append(k) ret.extend(self.get_all_dependees(k)) return ret def get_all_dependencies(self, node): """ Returns the list of all the nodes that are supported by the given one, recursively ! @param node: str """ ret = [] for k in self.get_dependencies(node): if k != self.ROOT: ret.append(k) ret.extend(self.get_all_dependencies(k)) return ret def depends_on(self, node, searched): """ Checks if a node depends on another. Recursive method. (might be limited by sys.getrecursionlimit()) """ if node is self.ROOT: return False #elif node is searched: # raise GraphError("Both given nodes are the same.") else: li = self.get_dependencies(node) for i in li: if i is searched: return True else: if i is not self.ROOT: res = self.depends_on(i, searched) if res: return True # if not found return False def _traverse(self, node, indent=0): """ Useful for printing an ASCII tree Recursive method ! (might be limited by sys.getrecursionlimit()) """ txt = " " * indent txt += " * %s\n" % (node) for child in self.get_supported_by(node): txt += self._traverse(child, indent + 2) return txt def __str__(self): txt = "DirectedGraph:\n" txt += str(self.__dict__) + "\n" txt += "Graph nodes:\n" txt += self._traverse(self.ROOT) return txt def iter_from_root_to_leaves(graph): """ Generator function to iterate through all nodes of a graph, from the root to its leaves, prioritizing each next node on the same level by the order in which it was added. @return: An iterator. """ current = graph.ROOT visited = [] # list of visited nodes. stack = [] # stack of iterators while True: if current not in visited: visited.append(current) # DO YOUR STUFF HERE yield current children = graph.get_supported_by(current) stack.append(iter(children)) try: current = stack[-1].next() except StopIteration: stack.pop() except IndexError: break lunch-0.4.0/lunch/master.py0000644000000000000000000007260711437735014014321 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ The Lunch master manages lunch slaves. Author: Alexandre Quessy """ import os import signal import socket import stat import time import sys import logging import warnings import subprocess # TODO: get rid of blocking IO from twisted.internet import defer from twisted.internet import error from twisted.internet import protocol from twisted.internet import reactor from twisted.internet import task from twisted.internet import utils from twisted.python import failure #from twisted.python import log from twisted.python import logfile from twisted.python import procutils from lunch import sig from lunch import graph from lunch.states import * from lunch import logger DEFAULT_LOG_DIR = "/var/tmp/lunch" log = None LOG_NAME = 'master' def start_stdout_logging(log_level='info'): #log.startLogging(sys.stdout) global log log = logger.start(level=log_level, name=LOG_NAME, to_stdout=True, to_file=False) class FileNotFoundError(Exception): """ Thrown when the given config file could not be found. """ pass class Master(object): """ The Lunch Master launches slaves, which in turn launch childs. """ def __init__(self, log_dir=DEFAULT_LOG_DIR, pid_file=None, log_file=None, config_file=None, verbose=False): """ @param log_dir: str Path. @param pid_file: str Path. @param log_file: str Path. @param config_file: str Path. """ # attributes: self.commands = {} # dict of str identifier: L{lunch.commands.Command} self.tree = graph.DirectedGraph() # For counting default names if they are none : self.i = 0 # IP to which not use SSH with : self.local_addresses = [ "localhost", "127.0.0.1", ] self._guess_local_ip_and_hostname_for_local_host() # These are all useless within this class, but might be useful to be read from the GUI: self.log_dir = log_dir self.pid_file = pid_file self.log_file = log_file self.config_file = config_file self.verbose = verbose self.main_loop_every = 0.05 # checks process to start/stop 20 times a second. self._time_now = time.time() self.launch_next_time = time.time() # time in future self._looping_call = task.LoopingCall(self.main_loop) self._looping_call.start(self.main_loop_every, False) self.wants_to_live = False # The master is either trying to make every child live or die. self.command_added_signal = sig.Signal() # param: Command object self.command_removed_signal = sig.Signal() # param: command object -- Called when actually deleted from the graph # actions: self.start_all() self._shutdown_event_id = reactor.addSystemEventTrigger("before", "shutdown", self.before_shutdown) #def __del__(self): # self._looping_call.stop() def _guess_local_ip_and_hostname_for_local_host(self): """ Lunch master guesses the hostname of the local machine, and should at least guess one IP (for one interface) It adds it to the list local_addresses so that it doesn't use SSH to launch command here, in case the programmer has added some commands on the localhost """ # TODO: When the username is different than the current one, we should use a different uid, gid or SSH to our own host. self.local_addresses.append(socket.gethostname()) try: self.local_addresses.append(socket.gethostbyname(socket.gethostname())) except socket.gaierror, e: log.error("Error getting IP of the local machine: " + str(e)) def start_all(self): """ Sets the master so that it starts all the slaves. """ log.debug("Using %s" % (__file__)) for c in self.commands.values(): c.enabled = True self.prepare_all_commands() self.wants_to_live = True def add_command(self, command): """ This method is wrapped (called) by the add_command function. @param command: L{lunch.commands.Command} object. """ # check if addr is local, set it to none if so. if command.host in self.local_addresses: log.info("Filtering out host %s since it is in list of local addresses." % (command.host)) command.host = None # set default names if they are none: if command.identifier is None: command.identifier = "default_%d" % (self.i) #TODO: use the first word of the command self.i += 1 while command.identifier in self.commands: # making sure it is unique command.identifier += "X" self.tree.add_node(command.identifier, command.depends) # Adding it the the dependencies tree. self.commands[command.identifier] = command # calls the signal self.command_added_signal(command) def prepare_all_commands(self): """ Called to change some attribute of all the commands before to start them for the first time. The config file is already loaded at this time. """ for c in self._get_all(): c.verbose = self.verbose def main_loop(self): """ Called in a looping call. This is actually the main loop of the application. Starting by the process with no dependency, starts them, in the order they were given, sleeping some time before each, as configured using their sleep_after attribute. The master is set up to either keep every child alive, or keep them dead. Stopping them is done as soon as possible. Starting them is done using the sequence described above. # get children of the root # get time now # if not started give them a time to be started, if it doesn't have one # if started, check if it has children # if so, give it a time to be started. """ # Trying to make all child live. (False if in the process of quitting) #orphans = self.tree.get_supported_by(self.tree.ROOT) #self._manage_siblings(orphans, should_run=self.wants_to_live) #log.info("----- Managing slaves LOOP ----") self._time_now = time.time() iterator = graph.iter_from_root_to_leaves(self.tree) for current in iterator: if current != self.tree.ROOT: self._treat_node(current) def _treat_node(self, node): """ Called once for each command on each main loop iteration. """ command = self.commands[node] all_dependencies = self.tree.get_all_dependencies(node) all_dependees = self.tree.get_all_dependees(node) has_dependees_to_wait_for = False # to wait so that they quit for dependee_name in all_dependees: dependee = self.commands[dependee_name] if dependee.child_state != STATE_STOPPED: has_dependees_to_wait_for = True # If RUNNING, check if we should stop it: if command.child_state == STATE_RUNNING: self._stop_node_if_needed(node) elif command.child_state == STATE_STOPPED: self._start_node_if_needed(node) self._stop_nodes_that_depend_on_this_one(node) if command.to_be_deleted: self._delete_command(node) def _node_has_dependees_that_are_stopped(self, node): """ Checks if this node has nodes which depend on it which are running. @rtype: C{bool} """ command = self.commands[node] all_dependees = self.tree.get_all_dependees(node) ret = False for dependee_name in all_dependees: dependee = self.commands[dependee_name] if dependee.child_state != STATE_STOPPED: ret = True return ret def _stop_nodes_that_depend_on_this_one(self, current_node): """ Pre-condition: Node is not running. (might have changed a second ago) """ all_dependees = self.tree.get_all_dependees(current_node) command = self.commands[current_node] if command.child_state == STATE_STOPPED: for dependee in all_dependees: other = self.commands[dependee] if other.child_state == STATE_RUNNING: other.stop() other.enabled = True # FIXME: that's very important def _stop_node_if_needed(self, node): """ Pre-condition: Node is running. """ command = self.commands[node] all_dependencies = self.tree.get_all_dependencies(node) all_dependees = self.tree.get_all_dependees(node) has_dependees_to_wait_for = self._node_has_dependees_that_are_stopped(node) if self.wants_to_live is False: command.stop() else: # check all node on which this node depends has_unsatisfied_dependency = False for dependency in all_dependencies: dep_command = self.commands[dependency] if dep_command.child_state != STATE_RUNNING and dep_command.respawn is False and dep_command.how_many_times_run != 0: has_unsatisfied_dependency = True break if has_unsatisfied_dependency: log.info("Got to stop %s since it has unsatisfied dependencies." % (command.identifier)) command.stop() def _start_node_if_needed(self, node): """ Pre-condition: Node is not running. """ command = self.commands[node] all_dependencies = self.tree.get_all_dependencies(node) all_dependees = self.tree.get_all_dependees(node) has_dependees_to_wait_for = self._node_has_dependees_that_are_stopped(node) # self.launch_next_time is for launching the next process... so it must be updated as # soon as we start one. if self.wants_to_live and self.launch_next_time <= self._time_now and command.enabled and command.is_ready_to_be_started(): if has_dependees_to_wait_for: # We cannot start this node if there are nodes that depend on this one to be running. pass #command.stop() else: start_it = True if not command.is_ready_to_be_started(): start_it = False if not command.respawn and command.how_many_times_run >= 1: start_it = False # already ran this once # # Do not start it if not enabled ! # (maybe lived for not long enough) #if not command.enabled: # start_it = False for dependency in all_dependencies: dep_command = self.commands[dependency] if dep_command.child_state != STATE_RUNNING and dep_command.respawn is True: start_it = False elif dep_command.respawn is False and dep_command.how_many_times_run == 0: start_it = False # Finally, start it if we are ready to. if start_it: self.launch_next_time = self._time_now + command.sleep_after log.info("Will start %s." % (command.identifier)) command.start() def _delete_command(self, node): """ Actually deletes it. """ ref = self.commands[node] del self.commands[node] #log.debug(self.commands) self.tree.remove_node(node) # XXX ? log.info("Removed command %s from the graph" % (node)) self.command_removed_signal(ref) ref.quit_slave() def _get_all(self): """ Returns all commands. """ return self.commands.values() def get_command(self, identifier): """ Returns a command identified by its identifier. Might raise a KeyError if it does not exist. @rtype: L{lunch.commands.Command} @param identifier: The identifier of the command to get. @type identifier: str """ return self.commands[identifier] def get_all_commands(self): """ Returns all commands, not grouped in any way. Used by the GUI. @rettype list """ return self._get_all() def stop_all(self): """ Stops all commands """ # TODO: use callLaters to check if stopped. #TODO: stop the looping call. _commands = self._get_all() self.wants_to_live = False for c in _commands: if c.child_state in [STATE_RUNNING, STATE_STARTING]: c.stop() else: log.info("Command %s is already stopped." % (c)) log.info("Done stopping all commands.") def remove_command(self, identifier): """ Removes a command """ if identifier in self.commands.keys(): command = self.commands[identifier] if command.get_state_info() == STATE_RUNNING: #FIXME command.stop() command.to_be_deleted = True def restart_all(self): """ Stops all commands, then wait until they are all done. Starts them all when ready. """ # TODO: use the looping call to do stuff in the future. self.stop_all() reactor.callLater(0.1, self._start_if_all_stopped) def _start_if_all_stopped(self): """ Checks in loop if all got stopped, if so, start them over. """ _commands = self._get_all() ready_to_restart = True for c in _commands: if c.child_state != STATE_STOPPED: ready_to_restart = False log.debug("Not yet ready to restart all since %s is still %s." % (c, c.child_state)) if ready_to_restart: self.start_all() log.info("Restarting all.") else: reactor.callLater(0.1, self._start_if_all_stopped) def quit_master(self): """ Stops all slaves and quits the application. """ if reactor.running: reactor.stop() def before_shutdown(self): """ Called before Twisted's shutdown. (end of master process) """ MAXIMUM_TIME_TO_WAIT = 20.0 if self.pid_file is not None: log.info("Will now erase the %s PID file" % (self.pid_file)) try: os.remove(self.pid_file) except OSError, e: log.error("Error removing lunch master PID file: " + str(e)) else: log.info("Erased %s" % (self.pid_file)) now = time.time() _shutdown_data = { "shutdown_started" : now, "shutdown_time": now + MAXIMUM_TIME_TO_WAIT } deferred = defer.Deferred() def _later(self, data): again = False for c in self._get_all(): if c.child_state == STATE_RUNNING: log.info("Please wait... Slave %s is still running." % (c.identifier)) again = True c.enabled = False if c.slave_state == STATE_RUNNING: # c.quit_slave() c.send_stop() if not again: log.info("All child processes are successfully stopped.") if time.time() >= (data["shutdown_time"]): log.info("Max shutdown time expired.", logging.ERROR) for c in self._get_all(): if c.child_state != STATE_STOPPED: log.critical("CHILD PROCESS %s IS IN STATE %s." % (c.identifier, c.child_state)) again = False # -------------------- Finally: if again: reactor.callLater(0.1, _later, self, data) else: log.info("Done stopping the Lunch Master.") deferred.callback(True) # stops reactor _later(self, _shutdown_data) return deferred def cleanup(self): """ Cleans up the reactor stuff. @rtype: L{twisted.defer.DeferredList} """ log.info("_cleanup the Master") deferreds = [] reactor.removeSystemEventTrigger(self._shutdown_event_id) # quit all slaves for command in self.get_all_commands(): if command.slave_state == STATE_RUNNING: deferreds.append(command.quit_slave()) # stop the master's loop if self._looping_call.running: d = self._looping_call.deferred self._looping_call.stop() # FIXME deferreds.append(d) return defer.DeferredList(deferreds) def _validate_identifier(identifier): """ Raises a RuntimeError if the identifier is not valid. Identifiers must not contain spaces or invalid characters for a file name. """ if " " in identifier: raise RuntimeError("Identifier must not contain spaces: %s" % (identifier)) if ":" in identifier: raise RuntimeError("Identifier must not contain colons: %s" % (identifier)) if "/" in identifier: raise RuntimeError("Identifier must not contain slashes: %s" % (identifier)) def gen_id_from_config_file_name(config_file_name="lunchrc"): """ Returns an identifier for the master using the config file name. Useful so that there is not two masters running with the same config file. @rettype str """ file_name = os.path.split(config_file_name)[1] # remove dir name identifier = file_name.replace(".", "") # getting rid of the dot in file name return identifier def gen_pid_file_path(identifier="lunchrc", directory="/var/tmp/lunch"): """ Returns a PID file name. Creates the directory if it does not exist. @return: Full path of the PID file for that master. """ file_name = "master-%s.pid" % (identifier) if not os.path.exists(directory): os.makedirs(directory) if not os.path.isdir(directory): raise RuntimeError("The path %s should be a directory, but is not." % (directory)) pid_file = os.path.join(directory, file_name) return pid_file def is_lunch_master_running(pid_file): """ Checks if a master is running, given its PID file. Removes the PID file if it's not running. @param pid_file: Full path of a PID file for a master. @return: PID of the master if running. None if not. """ if os.path.exists(pid_file): f = open(pid_file, 'r') pid = f.read() f.close() try: os.kill(int(pid), 0) # if it throws, it's dead except OSError: # no process with that ID os.remove(pid_file) return None except ValueError: # invalid int. (pidfile did not contain an int) os.remove(pid_file) return None else: # checks if it's really a lunch master that has this ID. command_check_master = "ps aux | grep %d | grep -v grep" % (int(pid)) #d = run_and_wait("bash", ["-c", command_check_master]) # blocking... it's easier to debug for now # TODO: get rid of subprocess here. output = subprocess.Popen(command_check_master, stdout=subprocess.PIPE, shell=True).communicate()[0] if "python" in output:# used to be "lunch", but changed it to "python", since lunch.master is now a livrary as well. return int(pid) else: #print "found PID, but it's not lunch!" os.remove(pid_file) return None else: return None def write_master_pid_file(identifier="lunchrc", directory="/var/tmp/lunch"): """ Writes master's PID in a file. Raises an error if a master with that PID already exists. @return: pid file name. """ # Check if there is already a master running pid_file = gen_pid_file_path(identifier, directory) if os.path.exists(pid_file): log.warning("PID file for master %s found!" % (pid_file)) pid = is_lunch_master_running(pid_file) if pid is not None: raise RuntimeError("There is already a Lunch Master running using the same configuration file. Its PID is %s" % (pid)) else: pass # Write our PID file f = open(pid_file, 'w') pid = os.getpid() f.write(str(pid)) f.close() os.chmod(pid_file, 0600) log.info("Wrote master's PID %d to file %s." % (pid, pid_file)) return pid_file def kill_master_if_running(identifier="lunchrc", directory="/var/tmp/lunch"): """ Given a lunch master identifier and a PID file directory, kills the master. """ pid_file = gen_pid_file_path(identifier, directory) deferred = defer.Deferred() send_sigkill_at = time.time() + 20.0 # wait 20 seconds before to use kill -9 is_first_time_called = True def _kill(is_first_time_called=False): #we check if running several time before to send it SIGKILL if os.path.exists(pid_file): log.info("PID file for master %s found!" % (pid_file)) pid = is_lunch_master_running(pid_file) if pid is not None: if is_first_time_called: log.warning("Sending SIGINT to the lunch master %s." % (identifier)) os.kill(pid, signal.SIGINT) reactor.callLater(0.2, _kill) else: if time.time() > send_sigkill_at: log.warning("Sending SIGKILL to the lunch master %s." % (identifier)) os.kill(signal.SIGKILL) deferred.callback(None) else: log.debug("The lunch master %s is not dead yet." % (identifier)) reactor.callLater(0.2, _kill) else: if is_first_time_called: log.warning("The lunch master %s was not running." % (identifier)) deferred.callback(None) else: if is_first_time_called: log.info("Could not find a PID file for master %s." % (identifier)) deferred.callback(None) reactor.callLater(0.01, _kill, True) return deferred def start_file_logging(identifier="lunchrc", directory="/var/tmp/lunch", log_level='info'): """ Starts logging the Master infos to a file. @rettype: str """ global log file_name = "master-%s.log" % (identifier) if not os.path.exists(directory): os.makedirs(directory) full_path = os.path.join(directory, file_name) f = open(full_path, 'w') f.close() os.chmod(full_path, 0600) #_log_file = logfile.BaseLogFile(file_name, directory) #_log_file = logfile.DailyLogFile(file_name, directory) #FIXME: do not use that DailyLogFile ! #log.startLogging(_log_file) full_path = os.path.join(directory, file_name) log = logger.start(level=log_level, name=LOG_NAME, to_stdout=True, to_file=True, log_file_name=full_path) return full_path #_log_file.path def chmod_file_not_world_writable(config_file): """ Make a file not writable by other users. """ mode = stat.S_IMODE(os.stat(config_file)[0]) new_mode = (mode & stat.S_IRUSR) + (mode & stat.S_IWUSR) + (mode & stat.S_IXUSR) # user hase read/write/execute permissions # 256, 128 and 64 try: os.chmod(config_file, new_mode) except OSError, e: log.warning("WARNING: Could not chmod configuration file. %s" % (e)) def execute_config_file(lunch_master, config_file, chmod_config_file=True): """ Reads the lunch file and execute it as Python code. Also makes it non-writable by everyone else, just in case. @param config_file: Path to the lunch file. (such as a .lunchrc) Might raise a FileNotFoundError. The functions to which the user can access in their lunch files are defined here. * add_command * add_local_address The user can also access the lunch_master variable, which is the Lunch Master. """ from lunch import commands def add_local_address(address): """ Adds an IP to which not use SSH with. :param address: str or list of str. IP address or host name """ # FIXME: what is that list thing? Why would we store lists of str? if type(address) is not list: addresses = [address] else: addresses = address for address in addresses: if address not in lunch_master.local_addresses: log.info("Adding %s in list of local addresses." % (address)) lunch_master.local_addresses.append(address) # -------------------------------- def add_command(command=None, identifier=None, env=None, user=None, host=None, group=None, order=None, sleep_after=0.25, respawn=True, minimum_lifetime_to_respawn=0.5, log_dir=None, sleep=None, depends=None, try_again_delay=0.25, give_up_after=0, ssh_port=None): """ This is the only function that users use from within the configuration file. It adds a Command instance to the list of commands to run. This function calls the Master.add_command static method, passing to it a L{lunch.commands.Command} object """ # TODO: remove priority and sleep kwargs in a future version log.debug("Adding %s (%s) %s@%s" % (identifier, command, user, host)) # ------------- warnings ------------------ if group is not None: raise RuntimeError("Groups are deprecated. Use dependencies instead.") if identifier is not None: _validate_identifier(identifier) if sleep is not None: raise RuntimeError("The sleep keyword argument has been renamed to sleep_after.") sleep_after = sleep #if priority is not None: # warnings.warn("The priority keyword argument does not exist anymore. Only the order in which add_command calls are done is considered.", DeprecationWarning) c = commands.Command(command=command, env=env, host=host, user=user, order=order, sleep_after=sleep_after, respawn=respawn, minimum_lifetime_to_respawn=minimum_lifetime_to_respawn, log_dir=log_dir, identifier=identifier, depends=depends, try_again_delay=try_again_delay, give_up_after=give_up_after, ssh_port=ssh_port) lunch_master.add_command(c) # ------------------------------------- #global _commands # is this necessary? if os.path.exists(config_file): if os.path.isdir(config_file): raise RuntimeError("The config file %s is a directory." % (config_file)) if chmod_config_file: chmod_file_not_world_writable(config_file) try: execfile(config_file) # config is plain python using the globals defined here. (the add_process function) except Exception, e: log.error("ERROR: Error in user configuration file.") raise else: # create the directory ? raise FileNotFoundError("ERROR: Could not find the %s file." % (config_file)) def start_logging(identifier='lunchrc', log_to_file=False, log_dir=DEFAULT_LOG_DIR, log_level='info'): """ Starts logging - either to a file or not. """ if log_to_file: log_file = start_file_logging(identifier=identifier, directory=log_dir, log_level=log_level) else: start_stdout_logging(log_level=log_level) log_file = None log.info("Started logging.") return log_file def run_master(config_file, log_to_file=False, log_dir=DEFAULT_LOG_DIR, chmod_config_file=True, verbose=False, log_level='info'): """ Runs the master that calls commands using ssh or so. This happens only on the master computer. * reads config file * uses multiprocessing to create many workers. (calling start_worker) Those worker launch the "lunch" program in a xterm terminal. (maybe through ssh, if on a remote host) * If ctrl-C is pressed from any worker, dies. @rettype Master Might raise a RuntimeError or a FileNotFoundError """ master_identifier = gen_id_from_config_file_name(config_file) # TODO: make this non-blocking. (return a Deferred) log_file = start_logging(identifier=master_identifier, log_to_file=log_to_file, log_dir=log_dir, log_level=log_level) pid_file = write_master_pid_file(identifier=master_identifier, directory=log_dir) log.debug("-------------------- Starting master -------------------") log.info("Using lunch master module %s" % (__file__)) lunch_master = Master(log_dir=log_dir, pid_file=pid_file, log_file=log_file, config_file=config_file, verbose=verbose) execute_config_file(lunch_master, config_file, chmod_config_file=chmod_config_file) # TODO: return a Deferred return lunch_master lunch-0.4.0/lunch.svg0000644000000000000000000002234511437735014013167 0ustar rootroot lunch-0.4.0/README0000644000000000000000000000367511437735014012222 0ustar rootrootThe Lunch distributed process launcher ====================================== Lunch is a distributed process launcher and manager for GNU/Linux. With Lunch, one can launch software processes on several different computers and make sure they keep running. This software was created to suit the needs of new media artists for live performances and interactive installations. It respawns the software that crash and provides a mean to manage dependencies between running processes. It provides the command-line lunch utility which can be invoked with a GTK+ user interface. See http://svn.sat.qc.ca/trac/lunch for more information. USING LUNCH =========== Here is a quick how-to. Make sure lunch is installed first. (see INSTALL) There should be a Lunch icon in the Application/Other Gnome menu. Copy the "config-sample" example config file to the local ~/.lunchrc :: cp doc/examples/config-sample ~/.lunchrc Edit the configuration file to suit your needs:: edit ~/.lunchrc Start the lunch master:: lunch A remote lunch-slave is started this way:: lunch-slave -i "xlogo" lunch-slave "xdg-open" Next, the lunch scripts controls the lunch-slave via its standard input and output. Type "help" in lunch-slave and press enter to see how the lunch-slave prompt is used by lunch. That prompt is not meant to be used directly by an operator, but rather only through lunch. The .lunch/config file is written in the Python language and the only function needed to be called there is add_command. Here are some examples:: add_command(command="xlogo", env={}, identifier="xlogo") add_command(command="mplayer /usr/share/example-content/Ubuntu_Free_Culture_Showcase/StopMotionUbuntu.ogv", env={}, identifier="mplayer") Setting the user and host arguments make it be issued through SSH to a remote host:: add_command(command="xlogo", env={"DISPLAY":":0.0"}, user=_user, host="example.org", identifier="remote_xlogo") See the examples for more information about this. lunch-0.4.0/NEWS0000644000000000000000000001164411437735014012034 0ustar rootrootVersion 0.3.5 - 2010-07-29 New features: * #83: Added SSH port to the options. (named ssh_port) * The text view in the user interface looks nicer * Made the detailled info resizable * Added a lot of comments * #48: Create an option to kill an already running master * #84: Detect when command to run doesn't exists on remote host Bug fixes: * #47: Many SSH errors could be handled properly * #82: Child pid is not updated the second time a child is started * Got rid of a deprecation warning for the GTK menu Version 0.3.4 - 2010-07-19 This release introduces critical bugfixes with stopping child processes. There is a new text view in the user interface that displays more information about the child processes. New features: * Better message at shutdown time * Added a textview to show command details * Storing the PID of the child Bug fixes: * #81: To kill a child, need to send the signal to the child, not the slave, otherwise, the slave crashes, and the child crashes with it. * Using SIGTERM, not SIGINT. * #80: Make sure we log until the process is dead. * #78: Increase delay between each signal when stopping a process. Version 0.3.3 - 2010-07-15 New features: * #76: Added the enabled keyword in the Command class. * Changed the title keyword for identifier * #61: GUI features for kill/retry (done in 0.3.2) Bug fixes: * Fixed #34: Make sure the log dir is a directory, not a file * Increased time before sending SIGKILL * #74: Dependees must be restarted their dependency is stopped * #77: Make sure all children are dead when we quit * #60: Try to restart children more than once (done in 0.3.2) * #17: Validate format of identifiers Version 0.3.2 - 2010-06-23 * Respawn after a little while instead of giving up * Do not ask confirmation to exit when no process is running. * Double the time to wait before trying again each time. Version 0.3.1 - 2010-06-18 * Modernized logging system * Checking for a lunch master that's a Python, not "lunch" * Created the confirm_and_quit method in the gui. * More logging for when we start processes * Fixed a traceback that was there since we updated the logging. * Added the show_about_dialog method * Decrease a lot the default verbosity. Use --verbose or --debug to increase it. * Fixed a crash when trying to log to a file. * Not using rotating log files anymore for the master Version 0.3.0 - 2010-06-16 * add/remove commands live * get the return value of the command once done * Update GUI to allow it to detect if there are new processes added * Do not copy commands in the GUI App class * Use a command_added and command_removed signal * Remove slot for state_change signal when a slave has been removed. * add the DONE, FAILED and GAVE UP states * improve reports on the state of processes * flush more often stdout/stderr to files Version 0.2.22 - 2010-06-01 * Lunch is now part of Debian Version 0.2.17 - 2010-01-25 * Use Trac with Mercurial * Split lunch in 4 files. * Port to Twisted. Drop multiprocessing. See http://twistedmatrix.com/trac/browser/tags/releases/twisted-9.0.0/twisted/runner/procmon.py * Get rid of the former lunch 0.1. Split the Master. Keep the slave in one file, for easier installation. * Use key=value pairs for env, drop JSON. * Drop sig.Signal in slave. * Split messages, use the first word as key, and the leftover as a line to pass to the right on_* method. * Use bash -c exec to run commands. * Remove a need for the lunch/constants file. * Re-implement starting order, with weight filtering from lowest to highest. * master: Make that master does not kill Slave when its child is dead. (configurable) * master: Simplify master states to 4: STOPPED, STARTING, RUNNING, STOPPING. * master: Use on_* callbacks style for the Slave Process Manager as well. * master: Use send_* methods style for the senders on both side. * master: The order of commands should be automatically ordered with calling order. * master: Dependencies: when one slave dies, kill all slaves who depend on it, restart them all again - if needed. * master: Wait for the greetings message, not the connection to the process stdin. * slave: Make sure each process is dead. Poll with a timer and use signals if needed. * slave: use a kill_delay option, with a default of 1.0 second. * master: Separate log for each slave on the master's side. * GTK+ GUI for the Master Lunch. Version 0.1.10 - 2010-01-13 * Using the multiprocessing Python module * Set up the basics of the lunch file * PID file for the master Initial draft - 2009-07-22 * Working proof of concept lunch-0.4.0/man_lunch-slave.txt0000644000000000000000000000060311437735014015143 0ustar rootroot[INTERACTIVE USAGE] Start lunch-slave. Type "help" and press enter to learn what other commands one can type. [HISTORY] 2010 - Ported from multiprocessing to Twisted 2009 - Written by Alexandre Quessy with contributions from Simon Piette [REPORTING BUGS] See http://svn.sat.qc.ca/trac/lunch for help and documentation. [SEE ALSO] lunch (1) lunch-0.4.0/COPYING0000644000000000000000000000152511437735014012365 0ustar rootrootLunch Copyright (C) 2008 Société des arts technologiques (SAT) http://www.sat.qc.ca All rights reserved. This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Lunch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lunch. If not, see . IMAGES The source of Lunch's icon is http://commons.wikimedia.org/wiki/File:Fruit-cherries.svg and is in the public domain. Thank you Rocket000 ! lunch-0.4.0/lunch.desktop0000644000000000000000000000032511437735014014033 0ustar rootroot[Desktop Entry] Exec=lunch -g -q Type=Application Terminal=false Categories=System;RemoteAccess;Network; Name=Lunch Comment=Distributed Process Launcher Comment[fr]=Launceur de processus distribué Icon=lunch.png lunch-0.4.0/lunch.png0000644000000000000000000000713611437735014013155 0ustar rootrootPNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATh{\y9ޙ]kr58I^IJ&iA4ZDMFJ6RT yT@ ˩b0klc{wvvc/ƎMks#9}9gEUyƺex;p^^`fzfuMy=(?1|"R l6hzG(T &}g 7P.#G# * bA{7ǃ %}pX311L%BwRt jIS(4@U\ғu=;ɰPUKY5߯9%ӛk6ŽZcgFѡ]߫X+ xqy*cf !n g-Ѥžf;޾%( xtrmE D C ut;Zz(X(+^~KOߩO.5D ]kju^ tnx@@&*#[VWK MD 1U[S`ɐ|lhm5DB 4WE+>5 [KΊ̷$ n+QgՏ_p cxczvBŧYvg-]* uECEpKx >glz+ŜQ hNUo#.Sx1&?P𨍹e=tz~ȸB7Wxm:XƊZzdĹ N^/-ck´ِG/-*Pc3[-]6u6F& \uD]/_ۓaidsa'ˏ% >ޤ?r~r*oA:: R{4-hH~wuᢧmzdCcH|g_ ;*W!sXN9[Pgۿ=o$C2l MTU} /~?4G!iD;u7d \+2fTˠZ #ݪX;?$.hU}Ϝ/Owm뮋o5n"Fb燬xȈh4":4J詾uv]s4jI;6y>|'+r߹qzF'xbޱ&i,{cu":"rW\Ak|J{ &K9 ttSj͔Z׉\|rFUvH)K"kQD2:ĪU9 ֹVZm6ё4M i[>R9!46HKh"xվYSJVT)~SZ`-#b§a<*KAc @3})gcz^ZPo *BfTU߅ c"U*sD! eymJO m#!p^R% ^SGYh#c0n /T*UM֩8&"b, CY@)uqe(ӊj)" @:՟ͪ."{AAOgڹUVa0ԙ963N-a "dxslTDƁz.F>exE_wi OG7ma20Ua+c>u;}U꬈ Wwwfg`!^Dg huT CP/= M=g8Y.uO;FDjz}OkW#kʢbMei{q\Pq<՗|",ʺ596!2'8@ $ ssh username@host 'echo Hello' The latter command should display "Hello" when issued. If you start lunch via SSH, and launch from there commands other hosts, you must first make sure that the ssh-agent is running and that you have unlocked your private SSH key. It can be done like this: $ ssh-agent bash $ ssh-add [HISTORY] 2010 - Ported from multiprocessing to Twisted 2009 - Written by Alexandre Quessy with contributions from Simon Piette [REPORTING BUGS] See http://svn.sat.qc.ca/trac/lunch for help and documentation. [SEE ALSO] lunch-slave (1) lunch-0.4.0/doc/0000755000000000000000000000000011437735014012074 5ustar rootrootlunch-0.4.0/doc/classes.dia0000644000000000000000000001153011437735014014210 0ustar rootroot]ms۸_~h|ɸI'ع:6q:soEHBCZ鴿ZHpclֲ$yp|5KRGѻq^/;8}):7T;ɲˣަ49Ii#i;hF{wi%j1-S{E_I@3U:3"jJmia jy1 ؈Ìo=ʴ'`!]lhM2vk:1DP"UJtFoTd Lsx< keQ8{AçR)S8 r%A@iT)`fL]2cV QQ($3MbBrdžCg**EI!*,6AF?Q P=@z(C P=TP!reRw_aNeJ4H\G88Pʊ͗ӳC@'tI= TtLyfhimO,r?#$2IJ>.w>ARSeD PN哀 A-xG#~S="9 %CHṭHA{\n6E'\0ïH@*R)A0h<,,,&"S"#y"h_c llla:`:`:O1#E<###XmqkuX]&fg/!8YnN%vW4d_jjdtWN'}tt*=<RVсlt`F6:сNZd; EH8;Y*k & P(XqGYpGY#q_* L!G9AXXX-ɂDZZXXX,L+<ˆlٗ/4@?OA†L`S~ 1Pm5t }_!f!%@ ءuÛ4tƂK`>'U#H9džALgZD;(N +igwjQIzHkQb˿S˲P~vbሂ[mZ"n]+h7|Xk7m6S4Uty 2vb=eqv0]pwh ,^2Gŗ/[ު]E,:]+r3'B 96!TVuUJ8[N,b-[5Y[i[[y[!y[%])]Hݼ5CjB݋HIf/;c;˅̏ͮ2\ݕbH8޸ B,t.,3UR@8lG-:ֆ.X$šM<cb\ض_ZJ돭B1hiZ R,] , ؈``X61'D&W8Or\TxN_>=ňRwxU Ŵ"W!xr9- ή!M4h9lǘfoeA(>휾9x98 vUz+`6ws$t +^.rWV)sh(_:m|߷IgQid,Юn%D@M0$/v6e)J$|ℑ.[J^ĊK]N'N%k$z̈́:4 S" Q}Z 8K (!h]6eEbP(\EPBre.ĨEWmcr6Lb6[ZD=W kLQRJ4 1 ʠ ʠ2(*2?zQG7 G^.Be8El4]GeP64ԉmx Ed6ቸ]ya@3~JzA6Џ b ! AHBBCBD2Q|- C]O ⤯̽"d8[[S[*]V,_ W`د~+_ W`;WFp1Xn릧0&B7Ki7ət&)>NH()io$|VvӴ'd3P4-Hs,% y2MVseX_ .xy pr 'ljxp ù1ù1ù1ùW-6Aa HjVmKw,xΥT J4Ş@7Aw;Нʺ;!baz@w;t'dr s`ڳro\_!~eAcgNڞmz:ȣ KxF!=hۜ%ٲ-dD n>f1ҾذYV: k=> Kݬ~QP&~mVB>h*x5*zm='6d%7o߈#F\~eL2b-oI$hpgqbD‡%? 3{{4ȀE3!E8΀v@;n&p{p(x@< ށ#osw;pwpv@CG;YUmx<wǢkh>q8pww3pw ~ r^ ).3̽˕T+bIS'F9=RWSYAd:$ml冒qɆ9UPE5PEUPEU3WVE]AQEί#&m ^Eb,slD:UW{T: r&) Ɂ@d"{k =Ad"{䊿Cz˒םo $2lunch-0.4.0/doc/process_dependencies.odg0000644000000000000000000004501611437735014016761 0ustar rootrootPKu 0<.++mimetypeapplication/vnd.oasis.opendocument.graphicsPKu 0<Configurations2/statusbar/PKu 0<'Configurations2/accelerator/current.xmlPKPKu 0<Configurations2/floater/PKu 0<Configurations2/popupmenu/PKu 0<Configurations2/progressbar/PKu 0<Configurations2/menubar/PKu 0<Configurations2/toolbar/PKu 0<Configurations2/images/Bitmaps/PKu 0<ϣ[[-Pictures/10000000000000200000002000309F1C.pngPNG  IHDR DPLTE3f333f3333f3ffffff3f̙3f3f333f333333333f33333333f33f3ff3f3f3f3333f33̙33333f3333333f3333f3ffffff3f33ff3f3f3f3fff3ffffffffffff3ffff̙fff3fffffff3ffffff3f333f3333f3ffffff3f̙̙3̙f̙̙̙̙3f3f̙333f3̙333f3fff̙fff3f̙̙3f̙3f̙3f333f3333f3ffffff3f̙3f3fOIDATxc'F*U0RU<4IENDB`PKu 0< content.xml]s)4j;ӤbwϱϴIoN:t&MJ"A\7'鳀`!lz9/ho}aU0ٲ8vJde:ay~ג5x|Ƌ͊Fa ';Ln&o"'Y&uo5 wn7w!I Ξ%s1usX<-*fGC3?&4@IF)?vLl ?r/fĶY[-ӭ7q dFd2ۥ]Z>V,RYݳx4N:f $rv!1O;ֵ]t.xe릢zMEļ+']v_k Df?TU]D\#e|]S%K~NX)`]F`Si[gpK˰5}~'{E9TnY._,6dzkZrSJfeI#^Wؐ}y9nޚLZK&Pnb7BV*-|#-9JN/@7Iϟ *z"%KxNdZ1)CϨLO!B(1@)Qyڶ~6O;IM[ *Êt<}3rX,3~;S u6B*Ix. 32#D=l^ߜu# xZyNA~RT+bEhffbwhͦGy[PMvYnMiߓmˁiLR2Db'^]掁РqmVdYL +1c9aR>jb8q->,-X7t?_a.ܽ|bZ~o %:#K5p?n=K}<}@ƻ;Qmiyo˞g୫rrdmo9Y1nVl"♩IU eAhoh i ONe4]8̳">?y٠$-·'sM֪˚M8 tŲi"+6w6d M吒f exh[.ng_XMϜ7Xb{êm}on6 օ- p"p*xMV,m}C(J Y{K^̠|Y:ਫ਼ In\:3v)P8ڄ37uE9<2Uʪ6L(ܚ|~ۓdy>goS2.?'&[\!׼&ps@~LLkF,/_6/|Cjά63ы?~|Z3_dӛٲq?xIi!}8bf$-ɕ3u{A?_R Zl7W[Q<1QV_mVw;y|r.BD,E:Te[1fZL\&D`Ӑ D`^0i?/_nU] TJMSz/9W~6 H "E^ BC91"E^ BShP'yTGg =Dz"=/Iy ܇MډNC޷0k$)5*zGIwƑ˒d;+>njU(5.7kA0E>N4H>!UPlRLB$EI8o8D/ 72󣖘TVD"NLΥDHeSURy !V>lSIUWV))v*E) P(,QYd$}NBdA}ꃌP}$FC(vPD%":l]P'if8~D~"?_/4nvz(Im`XB!t~{Cl RSTjBCފĒ5%%筺k9ln%UWoHsu6>UfSiFMX+աrqAV :جp~qǡwQgnM $l4HJ$%HyHD"BD""ȫBŎDV+5Y:HM&RٽD\5qR|2S+GGDd^'2[㐉^&"|lh==^&"ȼVdf54M&bk UFi"2D&y2Dd"2xJ~dji"Gf"3̳*ʦa8U&ζ9)uܑi*ew!brkQs_pA{yDkTsΞھl>P5YОK)=46T"f@vKL.fܹRt[<-j% 6+[DMNAbc<M9U,ze(EGWj"(5Tv&fO'* ܒ]nכvGaֶ!nDb"1/!&HL$&*9Ӿ͵ڈ-Dx"<0<3:1`Jd >9{ p9zhXj%cz~bGʜk+Fd{8O 9]0f>x,reO y0sJ=S/U8^&*DьxhŚ :`>`> plUTeElY1YYJ^3 5J%%|c)R`x}tHd$29x22*Fx/>"4 />X9&vBE\_2dնzQGɂx̻gii'UWdWAS*= Zk'❟OO~`vп H[:3Z;&dF<nD5|dp?IA,MW*BEHL{| <Ϋ8e?Gae'HX=zw‚_Cs^D=Ul,,M6 ͂4G,,l%|P,.,ifqfVA*.*qk4 4 ,,쇠UYvC*A@س";g>s0=|_YHq2Macp;oG+(`OId+Ψ`&6h**gc6'KGΟ_Ihh!nW2ZZEMMMMzא{F P&&NaVѶ KV**VѳU U؄Q`qV1X#J{7nDh qMVrVV~6 H1n~9n~vI;FGÙ2$}?'(~>4eFa8HL$5bZbid"2kAfI`EwOVDt":NǺx"ȼLdp"D`^0yԼGx"|5zFaOpCՉN@˦RmkDJ1OAk%NYtQ'NR7bJ [AաBuEJRR!ruyuQdSBj@p0#ݭ߫u**Z+BfKl%]!(~ЈjrviFf/^O:CWvOyTR%I&Lmz.l/%jz 6_M6w+ijy#0֠X[n0U7R, pS`kHF0tcg/}\i_FkFm7Nh8龆xT '_*ኵlSy=3@u-gES;E䇋*>ݬ4'p~PKS:`wcPKu 0< styles.xml\_60|hh7eS {hťׂh$ ^K}$7CeK^9uۅ6@ ÙWߧɂveyOzo^GlLYH +&08+y;-e2)+*Zevҕ^GZؽ:e[cj;:t7t0:HZH9U@go[vn>r777 G\^DKő+`xV6ee]2]19Ѭ _;_wu9R9ϴpUpWؔm^{Ưd:Y(ۂ*<lv !jUqYZvw'w+&xDF\]\aw)O[ =NB3>zu\ N(m5+"ˍ1L>vsE#x95yB-1*B|`T4[-z뿊׌kbY4oAm+!c*3TE[  : IжiD /QZǯ5Z$!0iW)Ybr+v+T  |/t?o~ ~U\[h[\E BnlInMHAڂʏLڿR{;v?{xOB2 WHۆ~##1-Z3N3MD) 둰*}.'iV<Y c]-ؑ@QJ{toYui{n?ˢzlMDUH]5òmk|Lsk\1\]9Hx׊+ގ:`qȝZټU-~{[2YH`ܚj?c(ҟ?i9IHo.ȸpobi58@c%5jvĐC*-uC$~L\NE࠺N`"'K CO0YI0G}iX+i_?ӕHW:QN{1C87qLcLV J9Мg$+S/nWBh~1Y,Z\g ggЋ!4>4N__ 37DyDW@+N6oP0m`S Tu@EZRM[0V1\2˰b&ŏӞ1W[8F@(JԏrXZMlIՍ3]I}~r~Mp]k >] ^f4.w~z` JbrwzZ! V9xr͡P="/Xoqd W ͥJbEjWd[T/Q~V4 m2.N^n?erh%b/~ugX[Z_t;q|6Oi15=U ng[,5XoYח/7] .|3MhF(y^eړ|j ;c 4vx;;OnZP6\9'Z.gUhR6PMOxscfχ UO:$Wƣ? hhQʯ\ݖӹ秚Es>s\T3ʙ?]̸.vs%\˰`nlu{QTΟvȜpl6s]`rvpm̮nXb.9#$oOF)nGR 5..V40e00;e#no6! 31w9VFR$'$ŎTKyvP*c$5?nB㣍;_ z PzݟtzPK.i܁ JPKu 0 OpenOffice.org/2.4$Linux OpenOffice.org_project/680m17$Build-9310aalex2010-01-15T10:40:12aalex2010-01-15T20:43:43aalex2010-01-15T12:07:5624PT10H3M32SPKu 0<Thumbnails/thumbnail.pngyeXTm:ux$bAKz@nF QZ}?=cu}ώ<''a \]MY@S{cueEC u]F˅Rqᢗ#y>KITs5X(i'䱵xwgRk8XYXإ/>]t˜n12rݑrbW"2g%5_@TMGFZ-oq TW;e/ $FQʿÆl9WՙNuͰA4hH:TYhtB7Zc0糑8x=In;3_X TdM27q%ApI$$$j6zi,_̺t| #MWs")xُe)ldXvg3c{7i}0|婱Y%x^BsV0Lsնw+`Ki:̩20M,})s!Z>^q[ Dv삥2kѿAwwWu4 M kIܺQx~ӵW.ݕ 6.YCyu$.헯o\XP!K,;^,AK +$R+{أ{(#{E++x9#D'f!@aa PeՅ# "DET)LMZ*. {88=*E8;a@aݨ`1ܲƃ3x7GډemOfLUXcYsYc\)ar O ?:j^Ћo_؏'ĊG1YLhʨ]o.XOo$<+Rĸ-zy\!nG4qi鉻M|Id|Fi4mqjrğ +J7G 67m-o *,Ap4]ts:. _']{Q\ze@C}lrUr+T)WL.P.߇-8JY`9C(jW4c8fW:qhIԸQTjzL8wRbۅg;n9X޽ZNbŜp% %~w7}_~Z JIuڒo\(GQiK0'|q[ e4N!ۯUܽo9sT&bk:wykvx8 f&D_V*+R9Mȴu<ƍ-yVlɶͰn32 ;T YcmC|&µlߙjU W΄ZKra6tv r:<"5HcWg+:]5GPn{GRckgXhwt؁bVHF H※?1@pmNGauL#\4ŸV’=|mWx^N HryZe nıG^2'_txpCLlN* x'_k-]6F/|,G֝5j_DZha׎ iF?$W7wwaTJɣ:pkA\#_x&W?܎8ʽDw'C!vQqO#>wvNW<ђ)2 p_64x0DŽQ 8TS3'Qb~^ "Y}z^EVųfA\)*h=L#Κ{PC<8yIiDX[䀾<D,*?bkb?T\Aw߄6~p  rϼ⇨Ͼ ~N2K>Szy"|ҊwznYȏR!B ĩur:SjdD qG;ο" T©+0jN<~;I`rH:־ܻGM7~5_k~I^E+; )+]ێ=6o&rKpݶc:DYg7p՟Ǘ)&WlXxp.[f.r|6&yDXiYh@TgxyU{J_0j=clGuHCg{0, LבֿH.a{ l H;;z$$ Z=ցGügځ_OÎ3o A%* H:=ʇp+ lEL4 jPL>/궬T#=[Ⱦ'jG!M=EvgIp_t:7i& ș3[*uq;g4:]X޻wXo@vhQc0̲cᓜ{,r¨ŒeQpJrR~|uW!( {؉k$ S |~q3fCJ2:,U¹\|/_SrrGQ[6=]a̬~H^_òhzEa@)w%ڱUv.h mZkihԦc6MV$7^ev YrB\UI`P7+~P2)E>h}4Bzp:ʈ8)L>7QӣPe F~ܳDi4c=!ܫbӈW1)砖5Ǚb]R|{"sڌ͋c@l;Nפ׽eB#-vؒ)n/_ZJR)fg`#>Ϛ`׋"~GM/[ 'iJ._ϕoK+{roRW7F2Y7B,znۓOz`fU`وDM~5VlCg.rnE %+A=i3 ?zqDn*cn̝T>~|!E?|,Ʃ3C_+ Z# WdS_Z%ѿl_ʁHՕܘaLUX%T-Ra@\sꠠ|Ё(.ԛg!,PvEԪ=Gv3or0V2ۂnѶ4z؊ל}t&֮)c4{@ X_@MLTaW[`atuʻ'DH9znO[|0G\.olEa+Zo!&5ecFdA3Lu 9*`8Zz(@D z-yﰑ04wOYX9W1d?(E"pMv4,HxkC*K˓ f&ڌC? wܤ.6*N8XjW:-VH~H daC|*{ZypKzdZS-v6?޶#z(4hث(g3$bBl5~<, 'B\^N_ +y9Nu;C9I7vpC:"qB9ic{ Lv:ї#'ǁK'"P;'9(ȲED {4BfFV+1)UA"y}ڜ?30r+Կ᧍-/.NX$gC{Q!ضT%֊:56o_Ɠ Ԑr$OsLmogʨaeW-2xtjoB9zZ,PP'} j )eR]s\U&?MEnG5GAFt,LVUO*]rS&>SC2=ɫl5؞ &ٝjBE&_p\) h=?2UȜ‰I?s: lizb8T΢K$؏hQio(Rc k `ҭөgnkC(EKz{ic,goӃcm03uUK &n@W6_Ѐ{eں2~ 4{ҧmV 2;-Ep;4;6f9w:9c~)ŠJAGIL\R % & @ Da=NΦ >5ӟS_JgdâCm߅D9BnW8Hx򢰋ԃdxmW}j!Wd ה3B z r D(egimJRWՁbF6{_ʥmzRf `+@{){ΥJ{]Z)'tvˊ>%U/ ^#!u3cĕ2&`Hx!""U2 ]称[YDoo>GBXD ϟb7 /"7ixiD1 6H s$&LFB"g&ޢC@Rg9_g#Л|@7`~L.6`sDxr$w xh}38Kʗ(e @ I0|U? Uݣ5\D]7ZrA.*/nYOp-p,`T)s{=Y2E HӢ:yCM!󿕄;B1=1~} ٻG_oWe|oˌMB: h˓*+!\8Z sا;E 0r B֧Ao4ӻ4r@HWvaJ =t G6ǚFw7+0J=z]vg祎PIw!9аfq?|zLѣ~ZԈj@>}$5bppnCqj೰n<qȪ@+HHE*F֙R?w+לZza@B]5$UcЮ2ݵ5H/9MN |,$@Dw9b#)@gVWgP+fw'w2#[5y>n{FI0Y^-t7Ȉɥϻ>ԥL%  ^&qC=xsDdƜ!z0*D&!B$5d4Dȯ2LDC >.LU38"']% .j΄3-&\H#ql8áoY[`s:%GAZ6JXoeN^>Hq?G h1eYm /3^nx2 ֤O٤)j;hG[LmPU73aiɵ[@zv:0Sլ lxӚ/j-Wq rXճwG_F=V&B+x铗}1ݡە hUoSlR΂jv16q\;x>ƨ6ި֯ZqQNvcSΤ2ÏKאxGR'60IIGx9{;d~n#2'_ɜ UPKJ.E%PKu 0<META-INF/manifest.xmlAo  u }$aߏfu) |A<*k*6/f,#mL[6aӲF5T^;Y\g6XFt%:0CeoZ=ewp4q?gwJtvP1VRPM]\\PQ^&h;AqG S64 .8{!2xo3f2+V$;낋 ${zt'݇ NdpՉ)ä5"Ba2w}LWBQ|>~A<{7E"7Hx_tRc"Uw.YNdtwӵ;q &~KxWPKyIePKu 0<.++mimetypePKu 0<QConfigurations2/statusbar/PKu 0<'Configurations2/accelerator/current.xmlPKu 0<Configurations2/floater/PKu 0<Configurations2/popupmenu/PKu 0<NConfigurations2/progressbar/PKu 0<Configurations2/menubar/PKu 0<Configurations2/toolbar/PKu 0<Configurations2/images/Bitmaps/PKu 0<ϣ[[-1Pictures/10000000000000200000002000309F1C.pngPKu 0jLq6_ήxQˣ|nΌA#:<6O:Ê:d׿F\!Fޞ%Κ|d3q<˳|4uZ`4/Lȓj(20lZ>kjid2q%eVk>}s$iJ Tg F7oG< ?G__|B|bM42vcyN"Fn`4OisCd6Y,m|·g2Q᫝oD=7Rn.ZWou N72Bz@ G ͦkhdP #8gşd\(]h C8-RߵM R/}@i2'jy! 2JfEeZy'g7_QeSUW%mmlSU (  GvrlYR`{P5 p߻52 ae8vx_P۴G$4Hn ]׆r fk1!XPQ&qądOreH+㎄ؓƠ e>4l Bҍ+WܘrZ 'aEǘP9dJd7߿X]q}%M4)M_p# xʄRK!% I-T!tEN7/ HO?\$BOEt\h9B "tusOU qM9]MANd)V@ hV9Vu尤/VX{۴jPFJ$ԉxT CZpN5!nre4wi:Ԙ%b"L@ )u~^0^ӆ3X1rT#V:B4蓮 ;Td^$[!`{11!CpׄCyUϐjW~ n_AQ}*rH I>_t]KD1Y)#%0[΋yeq\9r 4pG;pG ўG$Q5(= L䨾RDLw\EI(=>EӿUW_i-tK5 Nu 0ýgzgzg>G|y虇y/gV;lfT Zڥ.05:G+BqG/ќ]p^35F/He lx螨K?Wܖx`ϱ]MP5}Qpjk|, EJxyMam;;#.?/ h0--xc)xZ-:ҫ(VzQ9_ 渾)O]C@FgtgaW:¾K j{6ӹb A9Y(1!bP67kiyjih|@~J-g7JHo5N: X[xJwPKA;EPKv/<meta.xml OpenOffice.org/2.4$Linux OpenOffice.org_project/680m17$Build-9310aalex2010-01-07T17:32:59aalex2010-01-15T09:54:457PT12M40SPKv/<Thumbnails/thumbnail.pngxWT_Нtψ 9Đ1"HJCw( !ݩ ) ~·o:sZ}{?ωPS$!#@$J9-w \3kϨ$ig.n5&筦+Ua\k9P ɦ_VQ}iP{eтHee֊._~~_)뿵#mY>g#C ňz}k~K7GCfWڗ?IW"V|UIkj}5@LīKU9۹9 pD7Q@œP+o?Kt{r2Zt(A쳲–q4˳˵?>nАH!d\ynßz4l$F1}wJpX:hq{<"uD!hܫDSaTcE@ພnSB&y!=<8mS~xvܗ"{ovJ=%JYZSOSMߥ{{L~k+97.UnXWiƓmlTe)T9"y]Êrg d?!۸p扱 ppR53ş|&f oV-+Ћ$:[1S{ndW*2}l\Ng#v )Qi 0f~@Ofţnv%s͵`it_ 'ԫ DgadLޕmU.ݿ{r4^p-=Ό/:̭YJ,fWԞU~_}YgW<6et3s!K}~z߅VcSI(ZRfP3[0N">ӺHlmLM~}J$Nmc=&Z^kY쫱|%3b髁%˶r5 aYջg;%T%gwUxpx"sQ44Yuוק;Ԃ#Fv^8ԓ ^Y+}1. B2GΣ$ &L`PRC5v]+}b7'Yv=i6NbE_%ݬ%DZ?=Ƃ&*!PÎk#?dR>~EGXG C}d,!n-LSAUba ɖ--sfXFkp Co%˙T42EES)`^w<Ε;*TI'KxX JssVW7FY\̫ĒiYޜ7" `[ OPDr(<@ Ϧe4YkStXL8F%BƋUEd:p'0Ĩn׏PKZW.2KZw)jqjp{~Dz?y=b*%s@E$]fҬ}fY?*>P{U;ݬղR^GV^m7#RIqoό_= d>B-o'zNK^t=/M57AhnoLj$&.7:GvBBp}N6i`K9;MB'="+8ʺJT,(-:](\AgyS|(%f0%[l.DAρM69Ic%Ұzi@:rC%AnhX!84sew MʍEƠW k=F ӻ;]ׯ߶6dm] #_ 4: 㺇2&f>x೯p)8X:uߞO)Q&mkessoy;FbO.j@K?2'&OgpOa0p[gșVNN7SJ89pJi=`!Xxn|AAӷp\+ΖISჾ|pcs3n8ƜwY~8wo1Pt>o|L|Py;`4V _:h^J. :VV,b UKٝDHv0 wD\Xc+u̬62xKĩR@J'oi aFD-;􄰈{h\LeG-mF8gs˾Bi~QPDGc!L)7uqa̝"I×*Gqnwl-'뉀pT}G=Q(pF,v:M= ʍQΊW|G {o8Ѝ* 敽|)'0V8cC 8,gPkmu9>?pCO0c-tg:i9{Z,io9kWZbKR*;d%-bœcf=]i~#a.CrtR}j>]J2.Z}jB%]dR`#5SbeSKv,G#, ozaZYQ8eY6sj )~נSanhWn 0 R$n}t cgN⌛G/0f`S%W ۦ]L'j=#+2~jkQ 'c&a"XpMϳ"m.7CH"PL`!+)nt)3,0A4Es;=f bLo#~_-8^?~#rLƏdG {@84X#d OY.K02hh'}f:Cݾm[WEYapb NaR)#tݟO9V;#,]6ɗYPȦְ@]8;U&=g[ Co3j%}srK7hd*“0)&,|p* UJ.d=wyWRA<) ݊SRjxiJ1a-k$#,u7N=|XX ȃb*Z-sj5 H9>DL0ܗS'.)jjJJv(}rcg^O sn {OA,IQSnL.yͥ_b[Qr? P+U(ҕv Ո&2/Ctrq"E8Ǚ#( PB -vH\\uccҒ gn5.~0;|W` G&ȭH-&qNKtng1o g, ZK^cxDصhfvILqeps"_΢~Wtԥw6wsL/!LZ'KS'֤d8]Fs|I̷YnRl*OD;I .[hbשh1w7cZ'krrw!  TUo |YV@z>0d%sT}hKE!2,Q\k=~SL;(mOrNy6.܈M=:(]OA#d GE U3$IbϜALf ALzZGQ lxJQAB"Ƭ(0B1F*ɸJj"зeiOKb`\0۸~0?~Jjr2PK„&fPKv/< settings.xmlZ[s6~x>C.ۄ $l}%$+ `GF4stnkL^ hk׽# 1ZӤ_>b ʚ)qS\ny)MMbM4YtiVO^ -o.ebq8=f<y^p I#p9=3EY{ZoX)f?"Ƥ,^[ X(仳Bea 0wkm6i*St%Gc 7O}+Z4a~)zڵ!!d.x^wyy2-h+e㗲%5LCxp 2{ea1+W1v'+@d?e^{*}} %Y%O+PwG w$S(81=L@ze%r̈ѐZ:t_kj<pCp)0bdimȎˋKeT].&1DKOg]%_zmEN WE߇gԅY+bugL w=_TΔsձ7c *A <X(,N: ,U66i(jkT: 2j"Lxᆆ{Ws(bf"߫mv(L]u]a3]ƞ otQE;Lr9AcʨNpkp2J7Ո0L04NTRpbKP(dl'ChuH)+,[rHi<rCˡHG|Ʊj<,t̩?W9qc7BTY2V/ 21tR}^C#Lp 轭%S2`<8!{rVp;8LoPK>\sh#PKv/<META-INF/manifest.xmlKj0@=VU1q-&fW6X; HFi[S0Oͣ)k7vc^aaӠNZu`ZVzEdZ>T yb`yʝ뛣V4cՊą0$c.mʛwS<&Bϒ8b<(sT)i]EX|H!_Tʝz18{oIjN7IQׂpqr՛5\AkagMv@|6s-,2O\څ'.?PKANPKv/<.++mimetypePKv/<QConfigurations2/statusbar/PKv/<'Configurations2/accelerator/current.xmlPKv/<Configurations2/floater/PKv/<Configurations2/popupmenu/PKv/<NConfigurations2/progressbar/PKv/<Configurations2/menubar/PKv/<Configurations2/toolbar/PKv/<Configurations2/images/Bitmaps/PKv/\sh# 4/settings.xmlPKv/<ANq4META-INF/manifest.xmlPK5lunch-0.4.0/setup.py0000644000000000000000000000177411437735014013052 0ustar rootroot#!/usr/bin/env python """ setup.py for Lunch Also sets up the man pages. To update the version number : vim -o lunch/runner.py scripts/lunch-slave setup.py lunch/gui.py """ from setuptools import setup import subprocess import sys import lunch setup( name="lunch", version=lunch.__version__, description="The Lunch Distributed Process Launcher", author="SAT", author_email="alexandre@quessy.net", url="https://svn.sat.qc.ca/trac/lunch", packages=["lunch"], scripts=["scripts/lunch", "scripts/lunch-slave"] ) if sys.argv[1] == "build": commands = [ 'help2man --no-info --include=man_lunch.txt --name="Distributed process launcher" ./scripts/lunch --output=lunch.1', 'help2man --no-info --include=man_lunch-slave.txt --name="Process launcher" ./scripts/lunch-slave --output=lunch-slave.1' ] for c in commands: print("$ %s" % (c)) retcode = subprocess.call(c, shell=True) print("The help2man command returned %s" % (retcode)) lunch-0.4.0/CONTRIBUTING0000644000000000000000000000146611437735014013170 0ustar rootrootSome development notes about the Lunch project The release process ------------------- Update the documentation if needed: * scripts/lunch * scripts/lunch-slave * man_lunch.txt * README Make sure the version number is the next release's: * scripts/lunch-slave * lunch/__init__.py Run the unit tests: * trial lunch Update the release notes: * RELEASE Update the ChangeLog: * hg log > ChangeLog Create the tag and a tarball: * make sure you are in the right branch. * Commit any change * hg tag 0.4.0 * hg archive ../lunch-0.4.0 * cd .. * GZIP=--best tar -cvz --owner root --group root --mode a+rX -f lunch-0.4.0.tar.gz lunch-0.4.0/ * cd - Increase the version number (not yet released): * scripts/lunch-slave * lunch/__init__.py * NEWS: move the contents of the old RELEASE file to NEWS lunch-0.4.0/scripts/0000755000000000000000000000000011437735014013016 5ustar rootrootlunch-0.4.0/scripts/lunch0000755000000000000000000000237311437735014014062 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ Lunch executable script. Calls lunch.runner.run() """ import os import sys SCRIPTS_DIR = "scripts" def _is_in_devel(): d = os.path.split(os.path.dirname(os.path.abspath(__file__)))[1] return d == SCRIPTS_DIR if __name__ == "__main__": if _is_in_devel(): d = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0] sys.path.insert(0, d) os.environ["PATH"] += ":%s" % (os.path.join(d, SCRIPTS_DIR)) from lunch import runner runner.run() lunch-0.4.0/scripts/lunch-slave0000755000000000000000000006346611437735014015204 0ustar rootroot#!/usr/bin/env python # -*- coding: utf-8 -*- # # Lunch # Copyright (C) 2009 Société des arts technologiques (SAT) # http://www.sat.qc.ca # All rights reserved. # # This file is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Lunch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Lunch. If not, see . """ The lunch-slave script is an interactive process launcher. It that can only launch a single process. This file is a stand-alone script. It does not depend on any library, except Twisted and the standard Python modules. """ #FIXME: still need to edit this version string by hand __version__ = "0.4.0" DESCRIPTION = "The lunch slave utility is an interactive process launcher. It is intended to be run by the lunch master process through an encrypted SSH connection. It launches a single process at a time and allows to specify its environment and to log its standard output and error to a file. Launch it, type \"help\" and press enter to know more about how it works." #TODO: allow to launch more than one process. #TODO: spend more time looking at twisted.runner.procmon import os import sys import time import logging import textwrap from twisted.internet import protocol from twisted.internet import task from twisted.internet import error from twisted.internet import reactor from twisted.internet import stdio from twisted.protocols import basic from twisted.python import procutils # Those constants are redefined here to avoid the need to import the lunch module. STATE_STARTING = "STARTING" STATE_RUNNING = "RUNNING" # success STATE_STOPPING = "STOPPING" STATE_STOPPED = "STOPPED" # success class SlaveError(Exception): """ Raised by the Slave """ pass def call_callbacks(callbacks, *args, **kwargs): """ Calls each callable in the list of callbacks with the arguments and keyword-arguments provided. Simplified implementation of the signal-slot pattern. """ for c in callbacks: c(*args, **kwargs) class ChildProcess(protocol.ProcessProtocol): """ Process managed by a lunch-slave. Its stdout and stderr streams are logged to a file. """ def __init__(self, slave): """ @param slave: Slave instance. """ self.slave = slave def connectionMade(self): """ Called once the process is started. """ self.slave._on_connection_made() def outReceived(self, data): """ Called when text is received from the managed process stdout Twisted will not splitlines, it gives an arbitrary amount of data at a time. Here, we make sure our slave manager only gets one line at a time. """ for line in data.splitlines(): if line != "": self.slave._stdout_file.write(line + "\n") self.slave.must_flush_stdout_file = True if self.slave._num_lines_received == 0: if ": not found" in line: self.slave.on_command_not_found() self.slave._num_lines_received += 1 def errReceived(self, data): """ Called when text is received from the managed process stderr """ for line in data.splitlines().strip(): if line != "": self.slave._stdout_file.write(line + "\n") if self.slave._num_lines_received == 0: if ": not found" in line: self.slave.on_command_not_found() self.slave.must_flush_stdout_file = True self.slave._num_lines_received += 1 def processEnded(self, reason): """ Called when the child process has exited. status is probably a twisted.internet.error.ProcessTerminated "A process has ended with a probable error condition: process ended by signal 1" This is called when all the file descriptors associated with the child process have been closed and the process has been reaped. This means it is the last callback which will be made onto a ProcessProtocol. The status parameter has the same meaning as it does for processExited. """ exit_code = reason.value.exitCode if exit_code is None: exit_code = reason.value.signal self.slave._on_process_ended(exit_code) def inConnectionLost(self, data): #self.slave.log("stdin pipe has closed." + str(data)) pass def outConnectionLost(self, data): #self.slave.log("stdout pipe has closed." + str(data)) pass def errConnectionLost(self, data): #self.slave.log("stderr pipe has closed." + str(data)) pass def processExited(self, reason): """ This is called when the child process has been reaped, and receives information about the process' exit status. The status is passed in the form of a Failure instance, created with a .value that either holds a ProcessDone object if the process terminated normally (it died of natural causes instead of receiving a signal, and if the exit code was 0), or a ProcessTerminated object (with an .exitCode attribute) if something went wrong. """ self.slave.log("process has exited " + str(reason.value)) class Slave(object): """ Slave that manages a process. The command, identifier and env can be set after object creation. """ def __init__(self, command=None, identifier=None, env=None): """ @param command: Shell string. The first item is the name of the name of the executable. @param identifier: Any string. Used as a file name, so avoid spaces and exotic characters. """ self.flush_log_file_every = 0.1 self._num_lines_received = 0 self._process_transport = None self._child_process = None self._time_child_started = None self._child_running_time = None self._stdout_file = None self.must_flush_stdout_file = False self.child_state = STATE_STOPPED self.io_protocol = None # this attribute is set directly to the SlaveIO instance once created. self.command = command # string self.options = { "clear-old-logs": True, "delay_kill": 8.0, # seconds # TODO: use the attr of the lunch.commands.Command } self.identifier = identifier # title self.env = {} # environment variables for the child process if env is not None: self.env.update(env) self.log_dir = os.path.join(os.getcwd(), "lunch_log") self.pid = None self.log_callbacks = [] if self.identifier is None: self.identifier = "default" self.log_level = logging.DEBUG self._delayed_kill = None # DelayedCall instance self._flush_task = task.LoopingCall(self._looping_call_flush_log_files) self._flush_task.start(self.flush_log_file_every, now=False) def _before_shutdown(self): """ Called before twisted's reactor shutdown. to make sure that the process is dead before quitting. """ if self.child_state in [STATE_STARTING, STATE_RUNNING, STATE_STOPPING]: msg = "Child still %s. Stopping it before shutdown." % (self.child_state) self.log(msg) self.stop() def _looping_call_flush_log_files(self): if self.must_flush_stdout_file: if not self._stdout_file.closed: self._stdout_file.flush() self.must_flush_stdout_file = False def on_command_not_found(self): self.io_protocol.send_not_found() def is_alive(self): """ Checks if the child is alive. """ #TODO Use this if self.child_state == STATE_RUNNING: proc = self._process_transport try: proc.signalProcess(0) except (OSError, error.ProcessExitedAlready): msg = "Lost process %s. Error sending it an empty signal." % (self.identifier) self.io_protocol.send_error(msg) return False else: return True else: return False def start_child(self): """ Starts the child process """ if self.child_state in [STATE_RUNNING, STATE_STARTING]: msg = "Child is already %s. Cannot start it." % (self.child_state) self.io_protocol.send_error(msg) return elif self.child_state == STATE_STOPPING: msg = "Child is %s. Please try again to start it when it will be stopped." % (self.child_state) self.io_protocol.send_error(msg) # TODO: The Master should try again later. return if self.command is None or self.command.strip() == "": msg = "You must provide a command to be run." self.io_protocol.send_error(msg) return #else: # # find full path to executable # words = self.command.strip().split(" ") # executable_name = words[0] # try: # full_exec_path = procutils.which(executable_name)[0] # #self.command[0] = procutils.which(self.command[0])[0] # except IndexError: # msg = "Could not find path of executable %s." % (executable_name) # self.io_protocol.send_error(msg) # return # create log dir if not os.path.exists(self.log_dir): try: os.makedirs(self.log_dir) except OSError, e: self.io_protocol.send_error("Could not create log directory %s." % (self.log_dir)) return else: self.log("Created directory %s" % (self.log_dir)) # remove old log file stdout_file_name = os.path.join(self.log_dir, "child-%s.log" % (self.identifier)) if self.options["clear-old-logs"]: try: os.remove(stdout_file_name) # cleans it up from last time we ran it. TAKE CARE ! except OSError, e: self.log("Error erasing old stdout file %s." % (stdout_file_name), logging.ERROR) # open log file in write mode. try: self._stdout_file = file(stdout_file_name, "w") except OSError, e: self.io_protocol.send_error("Could not open log file %s in write mode." % (stdout_file_name)) return else: # close, chmod and re-open it: self._stdout_file.close() os.chmod(stdout_file_name, 0600) self._stdout_file = file(stdout_file_name, "a") self.log("Writing %s child's output to %s" % (self.identifier, stdout_file_name)) self.log("Slave %s will run command %s" % (self.identifier, str(self.command))) self._child_process = ChildProcess(self) proc_path = self.command[0] args = self.command environ = {} #for key in ['HOME', 'DISPLAY', 'PATH']: # passing a few env vars # if os.environ.has_key(key): # environ[key] = os.environ[key] # let's pass it all the environment variables. environ.update(os.environ) for key, val in self.env.iteritems(): environ[key] = val self.set_child_state(STATE_STARTING) #self.log("Identifier: %s" % (self.identifier)) #self.log("Environment variables: %s" % (str(environ))) #self._process_transport = reactor.spawnProcess(self._child_process, proc_path, args, environ, usePTY=True) shell = "/bin/sh" if os.path.exists("/bin/bash"): shell = "/bin/bash" self._time_child_started = time.time() self._num_lines_received = 0 self._process_transport = reactor.spawnProcess(self._child_process, shell, [shell, "-c", "exec %s" % (self.command)], environ, usePTY=True) self.pid = self._process_transport.pid self.log("Spawned child %s with pid %s." % (self.identifier, self.pid)) self.io_protocol.send_child_pid(self.pid) def _on_connection_made(self): if not STATE_STARTING: self.log("Connection made even if we were not starting the child process.", logging.ERROR) self.set_child_state(STATE_RUNNING) def stop(self): """ Stops the child process """ def _later_check(self, pid): if self.pid == pid: if self.child_state in [STATE_STOPPING]: self.io_protocol.send_error("Child process not dead.") self.stop() # KILL elif self.child_state in [STATE_STOPPED]: msg = "Successfully killed process after least than the %f seconds. State is %s." % (self.options["delay_kill"], self.child_state) self.log(msg) self._delayed_kill = None # TODO: do callLater calls to check if the process is still running or not. #see twisted.internet.process._BaseProcess.reapProcess signal_to_send = None if self.child_state in [STATE_RUNNING, STATE_STARTING]: self.set_child_state(STATE_STOPPING) self.log('Will stop the child process using SIGINT.') signal_to_send = 2 # Used to be 15 #"TERM" # closing files handles in _on_process_ended self._delayed_kill = reactor.callLater(self.options["delay_kill"], _later_check, self, self.pid) # XXX important. elif self.child_state == STATE_STOPPING: self.log('Will stop the child process using SIGKILL since it\'s not dead.') signal_to_send = 9 #"KILL" # _on_process_ended will probably be called, this time. else: # STOPPED msg = "The child process is already stopped." self.set_child_state(STATE_STOPPED) self.io_protocol.send_error(msg) return if signal_to_send is not None: try: self._process_transport.signalProcess(signal_to_send) except OSError, e: msg = "Error sending signal %s to the child process. %s" % (signal_to_send, e) self.io_protocol.send_error(msg) except error.ProcessExitedAlready: #if signal_to_send == "TERM": msg = "The child process had already exited while trying to send signal %s." % (signal_to_send) self.io_protocol.send_error(msg) #TODO: later, make sure it is correctly killed. def log(self, msg, level=logging.DEBUG): """ Sends some logging string to the Master. (through stdout) """ if level >= self.log_level: call_callbacks(self.log_callbacks, msg, level) def _on_process_ended(self, exit_code): self._child_running_time = time.time() - self._time_child_started if self.child_state == STATE_STOPPING: self.log('Child process exited as expected.') if self._delayed_kill is not None: if self._delayed_kill.active: self._delayed_kill.cancel() self._delayed_kill = None elif self.child_state == STATE_STARTING: self.log('Child process exited while trying to start it.') elif self.child_state == STATE_RUNNING: if exit_code == 0: self.log('Child process exited.') else: self.log('Child process exited with error.') self._process_transport.loseConnection() # close file handles self.log("Child exitted with %s" % (exit_code), logging.INFO) self.io_protocol.send_retval(exit_code) self.set_child_state(STATE_STOPPED) self.log("Closing slave's process stdout file.") self._stdout_file.close() def set_child_state(self, new_state): """ Handles state changes. """ if self.child_state != new_state: if new_state == STATE_STOPPED: self.log("Child lived for %s seconds." % (self._child_running_time)) self.io_protocol.send_state(new_state, self._child_running_time) else: self.io_protocol.send_state(new_state) self.log("child state: %s" % (new_state)) else: self.log("State is same as before: %s" % (new_state)) self.child_state = new_state class SlaveIO(basic.LineReceiver): """ Interactive commands for the slave using its standard input and output. """ delimiter = '\n' # unix terminal style newlines. remove this line # for use with Telnet _COMMAND_PREFIX = "recv" log_keys = { logging.DEBUG: "DEBUG", logging.INFO: "INFO", logging.WARNING: "WARNING", logging.CRITICAL: "CRITICAL", logging.ERROR: "ERROR", } def __init__(self, slave): self.slave = slave slave.io_protocol = self def connectionMade(self): self.send_message("Welcome to the lunch-slave console. Type 'help' for help.") if self._on_log not in self.slave.log_callbacks: self.slave.log_callbacks.append(self._on_log) self.send_ready() def send_not_found(self): self.sendLine("not_found %s" % (self.slave.command)) def send_ready(self): self.sendLine("ready") def send_child_pid(self, pid): self.sendLine("child_pid %s" % (pid)) def send_retval(self, exit_code): self.sendLine("retval %s" % (exit_code)) def _on_log(self, msg, level=logging.INFO): self.send_log(msg, level) def lineReceived(self, line): """ Commands are in the form "command arg" Answers are in the form "key message" """ if line == "": return # Parse the command try: key = line.split(" ")[0].lower() mess = line[len(key) + 1:] except IndexError, e: self.send_log("Index error parsing command-line. %s" % (e), logging.ERROR) # Dispatch the command to the appropriate method. Note that all you # need to do to implement a new command is add another recv_* method. try: method = getattr(self, 'recv_' + key) except AttributeError, e: self.send_error('%s no such command.') else: method(mess) def recv_help(self, line): """ help [command]: List commands, or show help on the given command. """ args = line.split() # If there is an argument, shows its method's __docstring__ if len(args) == 1: command = args[0] method_name = self._COMMAND_PREFIX + "_" + command if hasattr(self, method_name): doc = getattr(self, method_name).__doc__.strip() MAX_LINE_WIDTH = 70 for line in doc.splitlines(): if line != "": for line2 in textwrap.wrap(line, MAX_LINE_WIDTH): self.send_message("(help) " + line.strip()) else: self.send_log("(help) No such command: %s" % (command), logging.ERROR) else: commands = [cmd[len(self._COMMAND_PREFIX) + 1:] for cmd in dir(self) if cmd.startswith(self._COMMAND_PREFIX)] self.send_message("(help) Valid commands: %s" % (" ".join(commands))) def send_state(self, child_state, child_running_time=None): # TODO: could be more verbose - it's the child state, not the lunch-slave state. if child_running_time is not None: self.sendLine("%s %s %s" % ("state", child_state, child_running_time)) else: self.sendLine("%s %s" % ("state", child_state)) def recv_quit(self, line): """ quit: Quits this session """ if self.slave.child_state == STATE_RUNNING: self.send_log("The lunch-slave is still running. Need to stop it.") # XXX self.slave.stop() self.send_bye() self.transport.loseConnection() # close this process' stdin and stdout def send_bye(self): """ Confirms that we will stop this lunch-slave. """ self.sendLine('%s Exiting lunch-slave.' % ("bye")) def recv_logdir(self, line): """ logdir: sets the log directory """ words = line.split() # TODO: do not split it, use bash ! if len(words) == 0: self.send_error("No log dir specified.") return dir_name = words[0] if not os.path.exists(dir_name): try: os.makedirs(dir_name) except OSError, e: self.send_error("Directory %s does not exist and could not create it. %s" % (dir_name, e)) return self.slave.log_dir = dir_name self.send_ok() #def recv_set(self, name, value=None): # """set: Sets a configuration value. Usage: set """ # if hasattr(self.slave, name): # _type = type(getattr(self.slave, name)) # try: # setattr(self.slave, _type(value)) # except ValueError, e: # self.send_error("Bad type for %s. \"%s\" is not a valid %s." % (name, value, _type.__name__)) def recv_do(self, line): """ do: sets the shell command to be run. """ li = line.split() # TODO: do not split it, use bash ! if len(li) == 0: self.send_error("Cannot use an empty command.") return self.send_message("Using %s as a command to run." % (line)) self.slave.command = line.strip() self.send_ok() def recv_env(self, line): """ env: Sets the env vars for the process. Must be in a string of key=value separated by spaces. """ args = line.split() for key_val in args: try: k, v = key_val.split("=") except ValueError: self.send_error("%s %s" % ("Wrong env key-value pair:", key_val)) else: self.send_log("Setting env var $%s=%s." % (k, v), logging.DEBUG) self.slave.env[k] = v def send_ok(self): self.sendLine("ok") def recv_run(self, line): """ run: Starts the process. """ self.slave.start_child() def recv_stop(self, line): """ stop: Stops the process. """ self.slave.stop() def recv_opt(self, line): """ opt: sets an option. Usage: opt Use 0 or 1 for boolean options. """ words = line.split() try: k = words[0] v = words[1] except IndexError, e: self.send_error("Wrong number of arguments.") return if not self.slave.options.has_key(k): self.send_error("No such option: %s" % (k)) return current = self.slave.options[k] cast = type(current) try: if cast is bool: self.slave.options[k] = bool(int(v)) else: self.slave.options[k] = cast(v) self.send_ok() return except ValueError, e: self.send_error("Wrong type of value %s for option %s." % (v, k)) return # else ok? def recv_opts(self, line): """ opt: lists options. """ self.send_message("Options: %s" % (self.slave.options)) def recv_ping(self, line): """ ping: Answers with "pong" """ #TODO : check if slave process is running self.send_pong() def send_error(self, msg): self.sendLine("%s %s" % ("error", msg)) def send_message(self, msg): self.sendLine("%s %s" % ("msg", msg)) def send_pong(self): self.sendLine("pong") def send_log(self, msg, level=logging.DEBUG): key = self.log_keys[level] self.sendLine("%s %s %s" % ("log", key, msg)) def recv_status(self, line): """ status: ask the lunch-slave to print the state of the child process. """ self.send_status() def send_status(self): self.sendLine("%s %s" % ("status", self.slave.child_state)) def connectionLost(self, reason): # stop the reactor, only because this is meant to be run in Stdio. try: self.slave.log_callbacks.remove(self._on_log) # XXX ! except ValueError, e: pass if self.slave.child_state != STATE_STOPPED: try: self.slave.stop() except SlaveError, e: self.send_error("%s" % (e)) if reactor.running != 0: reactor.stop() def run_slave(): """ Runs the slave application. """ from optparse import OptionParser parser = OptionParser(usage="%prog [options]", version="%prog " + __version__, description=DESCRIPTION) parser.add_option("-i", "--id", type="string", help="Identifier of this lunch slave.") (options, args) = parser.parse_args() kwargs = {} if options.id: kwargs["identifier"] = options.id slave = Slave(**kwargs) reactor.addSystemEventTrigger("before", "shutdown", slave._before_shutdown) #to make sure that the process is dead before quitting. slave_io = SlaveIO(slave) stdio.StandardIO(slave_io) try: reactor.run() except KeyboardInterrupt: reactor.stop() if __name__ == "__main__": run_slave()