Log message for revision 71854:
  Updated release info:
  
  New Features:
  
  - Added support for setting environment variables in the configuration
    file.  This is useful when zdaemon is used to run programs that need
    environment variables set (e.g. LD_LIBRARY_PATH).
  
  - Added a command to rotate the transcript log.
  

Changed:
  U   zdaemon/trunk/CHANGES.txt
  U   zdaemon/trunk/README.txt
  U   zdaemon/trunk/setup.py
  U   zdaemon/trunk/src/zdaemon/README.txt
  U   zdaemon/trunk/src/zdaemon/component.xml
  U   zdaemon/trunk/src/zdaemon/schema.xml
  U   zdaemon/trunk/src/zdaemon/tests/tests.py
  U   zdaemon/trunk/src/zdaemon/zdctl.py
  U   zdaemon/trunk/src/zdaemon/zdrun.py

-=-
Modified: zdaemon/trunk/CHANGES.txt
===================================================================
--- zdaemon/trunk/CHANGES.txt   2007-01-10 10:16:55 UTC (rev 71853)
+++ zdaemon/trunk/CHANGES.txt   2007-01-10 11:36:01 UTC (rev 71854)
@@ -1,28 +1,18 @@
 zdaemon Changelog
 *****************
 
-To-Dos
-======
+zdaemon 2.0a2 (2007/01/08)
+==========================
 
-More docs:
+New Features
+------------
 
-- Document/demonstrate some important features, such as:
+- Added support for setting environment variables in the configuration
+  file.  This is useful when zdaemon is used to run programs that need
+  environment variables set (e.g. LD_LIBRARY_PATH).
 
-  - transcript log
+- Added a command to rotate the transcript log.
 
-  - working directory
-
-Features
-
-- environment variables
-
-- transcript log rotation
-
-Bugs 
-
-- help command
-
-
 zdaemon 2.0a1 (2006/12/21)
 ==========================
 
@@ -105,3 +95,16 @@
  - SVN tag:  svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1
 
  - Tagged to make better 'svn:externals' linkage possible.
+
+To-Dos
+======
+
+More docs:
+
+- Document/demonstrate some important features, such as:
+
+  - working directory
+
+Bugs 
+
+- help command

Modified: zdaemon/trunk/README.txt
===================================================================
--- zdaemon/trunk/README.txt    2007-01-10 10:16:55 UTC (rev 71853)
+++ zdaemon/trunk/README.txt    2007-01-10 11:36:01 UTC (rev 71854)
@@ -1,11 +1,10 @@
-****************************************************
-``zdaemon`` process controller for Unix-ased systems
-****************************************************
+*****************************************************
+``zdaemon`` process controller for Unix-based systems
+*****************************************************
 
-Overview
-********
+.. contents::
 
-'zdaemon' is a Python package which provides APIs for managing spplications
+'zdaemon' is a Python package which provides APIs for managing applications
 run as daemons.  Its principal use to date has been to manage the application
 server and storage server daemons for Zope / ZEO, although it is not limited
 to running Python-based applications (for instance, it has been used to

Modified: zdaemon/trunk/setup.py
===================================================================
--- zdaemon/trunk/setup.py      2007-01-10 10:16:55 UTC (rev 71853)
+++ zdaemon/trunk/setup.py      2007-01-10 11:36:01 UTC (rev 71854)
@@ -29,7 +29,6 @@
         entry_points=entry_points,
         include_package_data = True,
         install_requires=["ZConfig"],
-        tests_require=["zope.testing"],
         )
 except ImportError:
     from distutils.core import setup
@@ -38,7 +37,7 @@
 name = "zdaemon"
 setup(
     name=name,
-    version="2.0a1",
+    version="2.0a2",
     url="http://www.python.org/pypi/zdaemon";,
     license="ZPL 2.1",
     description=
@@ -53,7 +52,7 @@
         'Detailed Documentation\n'
         '**********************\n'
         + '\n' +
-        read('src', 'zdaemon', 'README.txt')
+        read('src/zdaemon/README.txt')
         + '\n' +
         'Download\n'
         '**********************\n'
@@ -61,5 +60,13 @@
 
     packages=["zdaemon", "zdaemon.tests"],
     package_dir={"": "src"},
+    classifiers = [
+       'Development Status :: 3 - Alpha',
+       'Intended Audience :: Developers',
+       'Intended Audience :: System Administrators',
+       'License :: OSI Approved :: Zope Public License',
+       'Topic :: Utilities',
+       'Operating System :: POSIX',
+       ],
     
     **setuptools_options)

Modified: zdaemon/trunk/src/zdaemon/README.txt
===================================================================
--- zdaemon/trunk/src/zdaemon/README.txt        2007-01-10 10:16:55 UTC (rev 
71853)
+++ zdaemon/trunk/src/zdaemon/README.txt        2007-01-10 11:36:01 UTC (rev 
71854)
@@ -7,7 +7,7 @@
 
 Using zdaemon requires specifying a number of options, which can be
 given in a configuration file, or as command-line options.  It also
-accepts commands teling it what do do.  The commants are:
+accepts commands teling it what do do.  The commands are:
 
 start
     Start a process as a daemon
@@ -27,6 +27,10 @@
 kill signal
     Send a signal to the daemon process
 
+reopen_transcript
+    Reopen the transcript log.  See the discussion of the transcript
+    log below.
+
 help command
     Get help on a command
 
@@ -137,6 +141,117 @@
     >>> system("./zdaemon -Cconf stop")
     daemon process stopped
 
+Environment Variables
+---------------------
+
+Sometimes, it is necessary to set environment variables before running
+a program.  Perhaps the most common case for this is setting
+LD_LIBRARY_PATH so that dynamically loaded libraries can be found.
+
+    >>> open('conf', 'w').write(
+    ... '''
+    ... <runner>
+    ...   program env
+    ...   socket-name /tmp/demo.zdsock
+    ... </runner>
+    ... <environment>
+    ...   LD_LIBRARY_PATH /home/foo/lib
+    ...   HOME /home/foo
+    ... </environment>
+    ... ''')
+
+    >>> system("./zdaemon -Cconf fg")
+    env
+    USER=jim
+    HOME=/home/foo
+    LOGNAME=jim
+    USERNAME=jim
+    TERM=dumb
+    PATH=/home/jim/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin
+    EMACS=t
+    LANG=en_US.UTF-8
+    SHELL=/bin/bash
+    EDITOR=emacs
+    LD_LIBRARY_PATH=/home/foo/lib
+
+Transcript log
+---------------
+
+When zdaemon run a program in daemon mode, it disconnects the
+program's standard input, standard output, and standard error from the
+controlling terminal.  It can optionally redirect the output to
+standard error and standard output to a file.  This is done with the
+transcript option.  This is, of course, useful for logging output from
+long-running applications.  
+
+Let's look at an example. We'll have a long-running process that
+simple tails a data file:
+
+    >>> f = open('data', 'w', 0)
+    >>> import os
+    >>> f.write('rec 1\n'); os.fsync(f.fileno())
+
+    >>> open('conf', 'w').write(
+    ... '''
+    ... <runner>
+    ...   program tail -f data
+    ...   transcript log
+    ... </runner>
+    ... ''')
+
+    >>> system("./zdaemon -Cconf start")
+    . daemon process started, pid=7963
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> import time
+    >>> time.sleep(0.1)
+
+Now, if we look at the log file, it contains the tail output:
+
+    >>> open('log').read()
+    'rec 1\n'
+    
+We can rotate the transcript log by renaming it and telling zdaemon to
+reopen it:
+
+    >>> import os
+    >>> os.rename('log', 'log.1')
+
+If we generate more output:
+
+    >>> f.write('rec 2\n'); os.fsync(f.fileno())
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> time.sleep(1)
+
+The output will appear in the old file, because zdaemon still has it
+open:
+
+    >>> open('log.1').read()
+    'rec 1\nrec 2\n'
+
+Now, if we tell zdaemon to reopen the file:
+
+    >>> system("./zdaemon -Cconf reopen_transcript")
+
+and generate some output:
+
+    >>> f.write('rec 3\n'); os.fsync(f.fileno())
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> time.sleep(1)
+
+the output will show up in the new file, not the old:
+
+    >>> open('log').read()
+    'rec 3\n'
+
+    >>> open('log.1').read()
+    'rec 1\nrec 2\n'
+
 Reference Documentation
 -----------------------
 
@@ -321,8 +436,3 @@
 In this example, log output is sent to a file and to standard out.
 Log output from zdaemon usually isn't very interesting but can be
 handy for debugging.
-
-
-
-
-

Modified: zdaemon/trunk/src/zdaemon/component.xml
===================================================================
--- zdaemon/trunk/src/zdaemon/component.xml     2007-01-10 10:16:55 UTC (rev 
71853)
+++ zdaemon/trunk/src/zdaemon/component.xml     2007-01-10 11:36:01 UTC (rev 
71854)
@@ -272,4 +272,11 @@
 
   </sectiontype>
 
+  <sectiontype name="environment" keytype="string">
+    <key name="+"
+         attribute="mapping"
+         required="no"
+         />
+  </sectiontype>
+
 </component>

Modified: zdaemon/trunk/src/zdaemon/schema.xml
===================================================================
--- zdaemon/trunk/src/zdaemon/schema.xml        2007-01-10 10:16:55 UTC (rev 
71853)
+++ zdaemon/trunk/src/zdaemon/schema.xml        2007-01-10 11:36:01 UTC (rev 
71854)
@@ -21,6 +21,8 @@
 
   <section name="*" type="runner" attribute="runner" required="yes" />
 
+  <section name="*" type="environment" attribute="environment" required="no" />
+
   <section name="*" type="eventlog" attribute="eventlog" required="no" />
 
 </schema>

Modified: zdaemon/trunk/src/zdaemon/tests/tests.py
===================================================================
--- zdaemon/trunk/src/zdaemon/tests/tests.py    2007-01-10 10:16:55 UTC (rev 
71853)
+++ zdaemon/trunk/src/zdaemon/tests/tests.py    2007-01-10 11:36:01 UTC (rev 
71854)
@@ -11,11 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""XXX short summary goes here.
 
-$Id$
-"""
-
 import os, re, shutil, sys, tempfile, unittest
 import ZConfig, zdaemon
 from zope.testing import doctest, renormalizing
@@ -98,6 +94,11 @@
     print o.read(),
 
 
+def checkenv(match):
+    match = [a for a in match.group(1).split('\n')[:-1]
+             if a.split('=')[0] in ('HOME', 'LD_LIBRARY_PATH')]
+    match.sort()
+    return '\n'.join(match) + '\n'
 
 def test_suite():
     return unittest.TestSuite((
@@ -112,6 +113,7 @@
             setUp=setUp, tearDown=tearDown,
             checker=renormalizing.RENormalizing([
                 (re.compile('pid=\d+'), 'pid=NNN'),
+                (re.compile('^env\n((\w+=[^\n]*\n)+)$'), checkenv),
                 ])
         ),
         ))

Modified: zdaemon/trunk/src/zdaemon/zdctl.py
===================================================================
--- zdaemon/trunk/src/zdaemon/zdctl.py  2007-01-10 10:16:55 UTC (rev 71853)
+++ zdaemon/trunk/src/zdaemon/zdctl.py  2007-01-10 11:36:01 UTC (rev 71854)
@@ -134,6 +134,13 @@
                     print "our program   =", program
                     print "daemon's args =", args
 
+        if (options.configroot is not None
+            and
+            options.configroot.environment is not None
+            ):
+            for k, v in options.configroot.environment.mapping.items():
+                os.environ[k] = v
+
     def emptyline(self):
         # We don't want a blank line to repeat the last command.
         # Showing status is a nice alternative.
@@ -286,6 +293,14 @@
             self.send_action("stop")
             self.awhile(lambda: not self.zd_pid, "daemon process stopped")
 
+    def do_reopen_transcript(self, arg):
+        if not self.zd_up:
+            print "daemon manager not running"
+        elif not self.zd_pid:
+            print "daemon process not running"
+        else:
+            self.send_action("reopen_transcript")
+
     def help_stop(self):
         print "stop -- Stop the daemon process."
         print "        If it is not running, do nothing."

Modified: zdaemon/trunk/src/zdaemon/zdrun.py
===================================================================
--- zdaemon/trunk/src/zdaemon/zdrun.py  2007-01-10 10:16:55 UTC (rev 71853)
+++ zdaemon/trunk/src/zdaemon/zdrun.py  2007-01-10 11:36:01 UTC (rev 71854)
@@ -74,6 +74,7 @@
 import socket
 import select
 import signal
+import threading
 from stat import ST_MODE
 
 if __name__ == "__main__":
@@ -349,6 +350,8 @@
         if pid:
             self.waitstatus = pid, sts
 
+    transcript = None
+
     def daemonize(self):
 
         # To daemonize, we need to become the leader of our own session
@@ -390,10 +393,7 @@
                                  % self.options.directory)
         os.close(0)
         sys.stdin = sys.__stdin__ = open("/dev/null")
-        os.close(1)
-        sys.stdout = sys.__stdout__ = open(self.options.transcript, "a", 0)
-        os.close(2)
-        sys.stderr = sys.__stderr__ = open(self.options.transcript, "a", 0)
+        self.transcript = Transcript(self.options.transcript)
         os.setsid()
         os.umask(self.options.umask)
         # XXX Stevens, in his Advanced Unix book, section 13.3 (page
@@ -626,6 +626,10 @@
                        "filename=%r\n" % self.proc.filename +
                        "args=%r\n" % self.proc.args)
 
+    def cmd_reopen_transcript(self, args):
+        if self.transcript is not None:
+            self.transcript.reopen()
+
     def cmd_help(self, args):
         self.sendreply(
             "Available commands:\n"
@@ -655,6 +659,40 @@
             self.logger.warn("Error sending reply: %s" % str(msg))
 
 
+class Transcript:
+
+    def __init__(self, filename):
+        self.read_from, w = os.pipe()
+        os.dup2(w, 1)
+        sys.stdout = sys.__stdout__ = os.fdopen(1, "a", 0)
+        os.dup2(w, 2)
+        sys.stderr = sys.__stderr__ = os.fdopen(2, "a", 0)
+        self.filename = filename
+        self.file = open(filename, 'a', 0)
+        self.write = self.file.write
+        self.lock = threading.Lock()
+        thread = threading.Thread(target=self.copy)
+        thread.setDaemon(True)
+        thread.start()
+
+    def copy(self):
+        lock = self.lock
+        i = [self.read_from]
+        o = e = []
+        while 1:
+            ii, oo, ee = select.select(i, o, e)
+            lock.acquire()
+            for fd in ii:
+                self.write(os.read(fd, 8192))
+            lock.release()
+            
+    def reopen(self):
+        self.lock.acquire()
+        self.file.close()
+        self.file = open(self.filename, 'a', 0)
+        self.write = self.file.write
+        self.lock.release()
+
 # Helpers for dealing with signals and exit status
 
 def decode_wait_status(sts):

_______________________________________________
Zope-Checkins maillist  -  Zope-Checkins@zope.org
http://mail.zope.org/mailman/listinfo/zope-checkins

Reply via email to