Commit 2987b932 authored by Chris Withers's avatar Chris Withers

Merge of my recent changes from the 2.12 branch:

- Ignore pyd's when they're built in place.
- add sphinx section to buildout and make.bat for Windows user
- de-cruft of tree
- revamp of docs
- rework of Windows service stuff to make zopeservice.py in instances unnecessary
- make zopectl work properly on Windows
parents 1b53d8d0 ea03022f
Place files containing code for External Method objects in this
directory.
...@@ -7,6 +7,7 @@ parts = ...@@ -7,6 +7,7 @@ parts =
alltests alltests
allpy allpy
dependencies dependencies
docs
extends = versions.cfg extends = versions.cfg
unzip = true unzip = true
...@@ -28,7 +29,6 @@ eggs = Zope2 ...@@ -28,7 +29,6 @@ eggs = Zope2
interpreter = zopepy interpreter = zopepy
scripts = zopepy scripts = zopepy
[alltests] [alltests]
recipe = zc.recipe.testrunner recipe = zc.recipe.testrunner
eggs = eggs =
...@@ -122,3 +122,7 @@ exclude = ...@@ -122,3 +122,7 @@ exclude =
ClientForm ClientForm
docutils docutils
mechanize mechanize
[docs]
recipe = zc.recipe.egg
eggs = sphinx
Credits
=======
The Zope software receives contributions from far and wide. Here's
the Zope Hall of Fame:
- Stephen Purcell allows us to distribute his PyUnit unit testing
framework with Zope.
- Jeff Bauer is Zope Dude Number One. Jeff took over PCGI and
kept pushing it forward through the years.
- Sam Rushing worked with us at Digital Creations to make Medusa
the publishing platform for ZServer and the concurrency of Zope2.
- A subset of windows guru Mark Hammond's win32 extensions are
bundled with win32 binary distributions of Zope.
- Martijn Pieters and Brian Hooper contributed the #in reverse
attribute.
- Phillip Eby contributed the DTML 'let' tag and many
other useful ideas, including the inspiration for the DTML
'call', 'with' and 'return'
tags.
- The DateTime module was based on work from Ted Horst.
- Jordan Baker contributed the 'try' tag, something we've wanted
for a long, long time.
- Martijn Pieters chipped in with a safe range function.
- Michael Hauser came up with the name "Zope".
- Eric Kidd from Userland contributed to ZPublisher's support for
XML-RPC.
- Andrew M. Kuchling wrote the initial version of mod_pcgi, making
him extremely cool in our book.
- Oleg Broytmann has taken up the standard of mod_pcgi and moving
it to be a really amazing thing, and ready for prime time.
- Jephte CLAIN made some patches to European ZopeTime.
- Thanks to Gregor Hoffleit for his work in getting Zope into the
Debian distribution.
- All the other Zopistas far and wide that stuck with us during
the Bobo/Principia days and politely push us to make the best damn
app server on this or any other planet.
- Of course the list of credits would be quite incomplete without
mentioning Guido van Rossum, benevolent dictator of Python and
long-time friend of Digital Creations. Zope Power is Python
Power.
- Special thanks to Richard Stallman and the Free Software
Foundation for their assistance and feedback on the
GPL-compatible 2.0 version of the Zope Public License.
======================================== ============================
Building and installing Zope from source Building and Installing Zope
======================================== ============================
Welcome to Zope! This document describes building and installing .. highlight:: bash
Zope on UNIX and Linux.
See ``doc/WINDOWS.rst`` for information about Windows. This document descibes how to get going with Zope.
Prerequisites Prerequisites
------------- =============
System requirements when building from source In order to use Zope, you must have the following pre-requisites
available:
- A supported version of Python, including the development support if - A supported version of Python, including the development support if
installed from system-level packages. Supported versions include: installed from system-level packages. Supported versions include:
...@@ -24,55 +24,100 @@ System requirements when building from source ...@@ -24,55 +24,100 @@ System requirements when building from source
headers installed which correspond to your system's ``zlib``. headers installed which correspond to your system's ``zlib``.
- A C compiler capable of building extension modules for your Python - A C compiler capable of building extension modules for your Python
(gcc recommended). (gcc recommended). This is not necessary for Windows as binary
releases of the parts that would need compiling are always made
available.
- If you wish to install Zope as a Service on Windows, you will need
to have the `pywin32`__ package installed.
Building Zope using zc.buildout __ https://sourceforge.net/projects/pywin32/
-------------------------------
Zope is built using the ``zc.buildout`` library, which needs to be Installing Zope
"bootstrapped" with your Python version. E.g.:: ===============
$ cd /path/to/zope Unless using buildout to build a zope instance as described
$ /path/to/your/python bootstrap/bootstrap.py :ref:`below <buildout-instances>`, you will need to install Zope
separately. If you want to create a buildout-based Zope instance,
please skip directly to that section.
Installing Zope using virtualenv
--------------------------------
Zope can be installed within a virtualized Python environment using
``virtualenv`` as follows::
$ virtualenv --no-site-packages my_zope
$ cd my_zope
$ source bin/activate
$ bin/easy_install -i http://download.zope.org/Zope2/index/<Zope version> Zope2
Using ``virtualenv`` is **highly recommended**. Otherwise, you may encounter
unexpected conflicts with packages that have already been installed.
Once you've installed Zope, you'll need to :ref:`create an instance <classic-instances>`.
Installing Zope using zc.buildout
---------------------------------
Unless you are `developing zope`__, you most likely
want to be creating a :ref:`buildout-based Zope instance <buildout-instances>` rather
that installing using buildout as described in this section.
The bootstrap script creates a ``buildout`` script in ``bin``; run this __ http://docs.zope.org/developer/
script to finish building Zope::
However, if you really just want to create Zope instances using the
classic ``mkzopeinstance`` but with the software installed by buildout,
then you need to do the following:
- Download the Zope 2 source distribution from `PyPI`__
__ http://pypi.python.org/pypi/Zope2
- Bootstrap the buildout
- Run the buildout
On Linux, this can be done as follows::
$ wget http://pypi.python.org/packages/source/Z/Zope2/Zope2-<Zope version>.tar.gz
$ tar xfvz Zope2-2.12.0.tar.gz
$ cd Zope2-2.12.0
$ /path/to/your/python bootstrap/bootstrap.py
$ bin/buildout $ bin/buildout
Once you've installed Zope, you'll need to :ref:`create an instance <classic-instances>`.
Installing Zope using easy_install Installing Zope using easy_install
---------------------------------- ----------------------------------
Zope can be installed using ``easy_install`` either using a global Zope can be installed using ``easy_install``, but it is recommended to
easy_install installation or within a virtualized Python environment use ``virtualenv`` as described above to avoid unexpected conflicts
(using ``virtualenv``):: with other packages installed directly in your python installation.
$ virtualenv --no-site-packages my_zope However, if you want to use easy_install globally, all you need to do
$ cd my_zope is::
$ source bin/activate
$ bin/easy_install -i http://download.zope.org/Zope2/index/<Zope version> Zope2
This will create the related scripts like ``mkzopeinstance`` within the $ easy_install -i http://download.zope.org/Zope2/index/<Zope version> Zope2
``bin`` folder of your global or virtualized Python environment.
Using ``virtualenv`` is **highly recommended**. Otherwise you may encounter This will create the related scripts such as ``mkzopeinstance`` within the
unexpected conflicts with already installed packages. scripts folder of your python installation. You can then use them to
create instances as described below.
.. _classic-instances:
Creating a Zope Instance Creating a classic Zope Instance
------------------------ ================================
Once you've performed the install step, to begin actually using Once you've installed Zope, you will need to create an "instance
Zope, you will need to create an "instance home", which is a home". This is a directory that contains configuration and data for a
directory that contains configuration and data for a Zope server Zope server process. The instance home is created using the
process. The instance home is created using the ``mkzopeinstance`` ``mkzopeinstance`` script::
script::
$ bin/mkzopeinstance $ bin/mkzopeinstance
If you use Zope from SVN, you will need to specify the Python interpreter You can specify the Python interpreter to use for the instance
to use for the instance explicitly:: explicitly::
$ bin/mkzopeinstance --python=$PWD/bin/zopepy $ bin/mkzopeinstance --python=$PWD/bin/zopepy
...@@ -82,8 +127,97 @@ command-line options, run the script with the ``--help`` option:: ...@@ -82,8 +127,97 @@ command-line options, run the script with the ``--help`` option::
$ bin/mkzopeinstance --help $ bin/mkzopeinstance --help
.. note::
The traditional "inplace" build is no longer supported. If using
``mkzopeinstance``, always do so outside the buildout/virtualenv
environment. If you wish to manage your Zope instance using
buildout, please see the section below.
.. _buildout-instances:
Creating a buildout-based Zope Instance
=======================================
If you wish to use buildout to manage your Zope instance, then the
instance is created as follows:
* Create a directory for your instance. In this directory, create a
``etc``, ``logs`` and ``var`` subdirectories.
* Download the following file into your instance directory:
`http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py`__
__ http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py
.. highlight:: none
* Create a buildout configuration as follows:
.. topic:: buildout.cfg
:class: file
::
[buildout]
parts = instance
extends = http://svn.zope.org/*checkout*/Zope/tags/<Zope version>/versions.cfg
[instance]
recipe = zc.recipe.egg
eggs = Zope2
interpreter = py
scripts = runzope zopectl
initialization =
import sys
sys.argv[1:1] = ['-C',r'${buildout:directory}/etc/zope.conf']
This is the minimum but all the usual buildout techniques can be
used.
* Create a Zope configuration file starting as follows:
.. topic:: etc/zope.cfg
:class: file
::
%define INSTANCE <path to your instance directory>
python $INSTANCE/bin/py[.exe on Windows]
instancehome $INSTANCE
.. highlight:: bash
* Now, run the following commands::
$ /path/to/your/python bootstrap.py
$ bin/buildout
In the ``bin`` subdirectory of your instance directory, you will
find ``runzope`` and ``zopectl`` scripts that can be used as
normal.
Using your Zope instance
========================
Starting Zope as a Daemon There are various ways to run Zope from your newly created
instance. They are all described below.
Running Zope in the Foreground
------------------------------
To run Zope without detaching from the console, use the ``fg``
command (short for ``foreground``)::
$ /path/to/zope/instance/bin/zopectl fg
In this mode, Zope emits its log messages to the console, and does not
detach from terminal.
Running Zope as a Daemon
------------------------- -------------------------
Once an instance home has been created, the Zope server can now be Once an instance home has been created, the Zope server can now be
...@@ -95,23 +229,47 @@ During start, zope emits log messages into ./log/event.log ...@@ -95,23 +229,47 @@ During start, zope emits log messages into ./log/event.log
You can examine it with the usual tools (cat, more, tail) You can examine it with the usual tools (cat, more, tail)
and see if there are any errors preventing zope from starting. and see if there are any errors preventing zope from starting.
.. highlight:: none
.. note::
Running Zope in the Foreground For this to work on Windows, the Zope instance must be installed as
------------------------------ a Service. This is done with::
By default, ``zopectl start`` will start a background process (a bin\zopectl install
"daemon) that manages Zope. ``zopectl stop`` will stop the background
process. To run Zope without detaching from the console, use the ``fg``
command (short for ``foreground``)::
$ /path/to/zope/instance/bin/zopectl fg If you later want to remove this Service, do the following::
In this mode, Zope emits its log messages to the console, and does not bin\zopectl remove
detach from terminal.
For the full list of options available for setting up Zope as a
Windows Service, do::
bin\zopectl install --help
.. highlight:: bash
Integrating with System Startup
-------------------------------
zopectl can be linked as rc-script in the usual start directories
on linux or other System V unix variants.
You can use ``zopectl`` interactively as a command shell by just
calling it without any arguments. Try ``help`` there and ``help <command>``
to find out about additionally commands of zopectl. These commands
also work at the command line.
.. note::
On Windows, a Service can be installed and set to start
automatically with the following:
.. code-block:: none
bin\zopectl install --startup=auto
Configuring Zope Configuring Zope
---------------- ================
Your Zope instance is configured through a file, either found by Your Zope instance is configured through a file, either found by
default:: default::
...@@ -127,8 +285,8 @@ or passed explicitly on the commandline:: ...@@ -127,8 +285,8 @@ or passed explicitly on the commandline::
Config file: /tmp/other.conf Config file: /tmp/other.conf
When starting Zope, if you see errors indicating that an address is in When starting Zope, if you see errors indicating that an address is in
use, then you will have to supply arguments to runzope to change the ports use, then you may have to change the ports Zope uses for HTTP or FTP.
used for HTTP or FTP. The default HTTP and FTP ports used by Zope are The default HTTP and FTP ports used by Zope are
8080 and 8021 respectively. You can change the ports used by 8080 and 8021 respectively. You can change the ports used by
editing ./etc/zope.conf appropriately. editing ./etc/zope.conf appropriately.
...@@ -143,21 +301,8 @@ The section in the configuration file looks like this:: ...@@ -143,21 +301,8 @@ The section in the configuration file looks like this::
The address can just be a port number as shown, or a host:port The address can just be a port number as shown, or a host:port
pair to bind only to a specific interface. pair to bind only to a specific interface.
Integrating with System Startup
-------------------------------
zopectl can be linked as rc-script in the usual start directories
on linux or other System V unix variants.
You can use ``zopectl`` interactively as a command shell by just
calling it without any arguments. Try ``help`` there and ``help <command>``
to find out about additionally commands of zopectl. These commands
also work at the command line.
Logging In To Zope Logging In To Zope
------------------ ==================
Once you've started Zope, you can then connect to the Zope webserver Once you've started Zope, you can then connect to the Zope webserver
by directing your browser to:: by directing your browser to::
...@@ -172,6 +317,11 @@ You will be prompted for a user name and password. Use the user name ...@@ -172,6 +317,11 @@ You will be prompted for a user name and password. Use the user name
and password you provided in response to the prompts issued during and password you provided in response to the prompts issued during
the "make instance" process. the "make instance" process.
If you are using a buildout-based Zope instance, you will need to
create a user as follows::
$ bin/zopectl adduser username password
Now you're off and running! You should be looking at the Zope Now you're off and running! You should be looking at the Zope
management screen which is divided into two frames. On the left you management screen which is divided into two frames. On the left you
can navigate between Zope objects and on the right you can edit them can navigate between Zope objects and on the right you can edit them
...@@ -180,12 +330,10 @@ of the frame. ...@@ -180,12 +330,10 @@ of the frame.
If you haven't used Zope before, you should head to the Zope web If you haven't used Zope before, you should head to the Zope web
site and read some documentation. The Zope Documentation section is site and read some documentation. The Zope Documentation section is
a good place to start. You can access it at: a good place to start. You can access it at http://docs.zope.org/
http://docs.zope.org/
Troubleshooting Troubleshooting
--------------- ===============
- This version of Zope requires Python 2.5.4 or better, including - This version of Zope requires Python 2.5.4 or better, including
2.6.x. It will *not* run with Python 3.x. 2.6.x. It will *not* run with Python 3.x.
...@@ -201,4 +349,4 @@ Troubleshooting ...@@ -201,4 +349,4 @@ Troubleshooting
you built Python from source all the configuration information you built Python from source all the configuration information
should already be available. should already be available.
- See ``doc/CHANGES.rst`` for important notes on this version of Zope. - See the :doc:`CHANGES` for important notes on this version of Zope.
Setting the initial user name and password Filesytem Permissions
========================================== =====================
Because Zope is managed through the web, user names and passwords must be You need to set permissions on the directory Zope uses to store its
used to assure that only authorized people can make changes to a Zope data. This will normally be the `var` directory in the instance home.
installation. Zope needs to read and write data to this directory. Before
Some user name and password is needed to "bootstrap" the creation of
normal managers of your Zope site. This is accomplished through the
use of the file 'inituser'. The first time Zope starts, it will detect
that no users have been defined in the root user folder. It will search
for the 'inituser' file and, if it exists, will add the user defined
in the file to the root user folder.
Normally, 'inituser' is created by the Zope install scripts. Either
the installer prompts for the password or a randomly generated
password is created and displayed at the end of the build script.
You can use the 'zpasswd.py' script to create 'inituser' yourself.
Execute 'zpasswd.py' like this::
python zpasswd.py inituser
The script will prompt you for the name, password, and allowed
domains. The default is to encode the password with SHA, so please
remember this password as there is no way to recover it (although
'zpasswd.py' lets you reset it.)
In some situations you may need to bypass normal security controls
because you have lost your password or because the security settings
have been mixed up. Zope provides a facility called an "emergency
user" so that you can reset passwords and correct security
settings.
The emergency user password must be defined outside the application
user interface. It is defined in the 'access' file located
in the Zope directory. It should be readable only by the user
as which your web server runs.
To create the emergency user, use 'zpasswd.py' to create the
'access' file like this::
python zpasswd.py access
In order to provide a somewhat higher level of security, various
encoding schemes are supported which provide access to either SHA-1
encryption or the standard UNIX crypt facility if it has been compiled
into Python. Unless you have some special requirements (see below),
you should use the SHA-1 facility, which is the default.
Format of 'inituser' and 'access'
---------------------------------
A password file should consist of a single line of the form::
name:password
Note that you may also add an optional third component to the line in the
access file to restrict access by domain. For example, the line::
mario:nintendoRules:*.mydomain.com
in your 'access' file will only allow permit emergency user access
from `*.mydomain.com` machines. Attempts to access the system from
other domains will fail, even if the correct emergency user name
and password are used.
Please note that if you use the ZServer monitor capability, you will
need to run with a clear text password.
Setting permissions on the var directory
----------------------------------------
You need to set permissions on the Zope var directory.
Zope needs to read and write data from its var directory. Before
running Zope you should ensure that you give adequate permissions running Zope you should ensure that you give adequate permissions
to the Zope var directory for the userid Zope will run under. to this directory for the userid Zope will run under.
Depending on how you choose to run Zope you will need to give Depending on how you choose to run Zope you will need to give
different permissions to the var directory. If you use Zope with an different permissions to the directory. If you use Zope with an
existing web server, it will probably run Zope as 'nobody'. In this existing web server, it will probably run Zope as 'nobody'. In this
case 'nobody' needs read and write permissions to the var directory. case 'nobody' needs read and write permissions to the var directory.
If you change the way you run Zope you may need to modify the permissions If you change the way you run Zope, you may need to modify the permissions
of the var directory and the files in it to allow Zope to read and write of the directory and the files in it to allow Zope to read and write
under its changed userid. under its changed userid.
...@@ -2,9 +2,9 @@ Zope effective user support ...@@ -2,9 +2,9 @@ Zope effective user support
=========================== ===========================
.. note:: .. note::
It is best practice running Zope behind a reverse proxy like It is best practice to run Zope behind a reverse proxy like
Apache, Squid or Varnish. In this case you do not need to run Apache, Squid or Varnish. In this case, you do not need to run
or install Zope with root privileges since the reverse proxy or install Zope with root privileges, since the reverse proxy
will bind to port 80 and proxy back all request to Zope running will bind to port 80 and proxy back all request to Zope running
on an unpriviledged port. on an unpriviledged port.
......
Special Users
=============
Because Zope is managed through the web, user names and passwords must be
used to assure that only authorized people can make changes to a Zope
installation.
Adding Managers
---------------
If you need to add a Manager to an existing Zope instance, you can do
this using `zopectl` as follows::
zopectl adduser `name` `password`
The Initial User
----------------
An initial username and password is needed to "bootstrap" the creation of
normal managers of your Zope site. This is accomplished through the
use of the 'inituser' file in the directory specified as the instance
home.
The first time Zope starts, it will detect
that no users have been defined in the root user folder. It will search
for the 'inituser' file and, if it exists, will add the user defined
in the file to the root user folder.
Normally, 'inituser' is created by the Zope install scripts. Either
the installer prompts for the password or a randomly generated
password is created and displayed at the end of the build script.
You can use the 'zpasswd.py' script to create 'inituser' yourself.
Execute 'zpasswd.py' like this::
python zpasswd.py inituser
The script will prompt you for the name, password, and allowed
domains. The default is to encode the password with SHA, so please
remember this password as there is no way to recover it (although
'zpasswd.py' lets you reset it.)
The Emergency User
------------------
In some situations you may need to bypass normal security controls
because you have lost your password or because the security settings
have been mixed up. Zope provides a facility called an "emergency
user" so that you can reset passwords and correct security
settings.
The emergency user password must be defined outside the application
user interface. It is defined in the 'access' file located
in the Zope directory. It should be readable only by the user
as which your web server runs.
To create the emergency user, use 'zpasswd.py' to create the
'access' file like this::
python zpasswd.py access
In order to provide a somewhat higher level of security, various
encoding schemes are supported which provide access to either SHA-1
encryption or the standard UNIX crypt facility if it has been compiled
into Python. Unless you have some special requirements (see below),
you should use the SHA-1 facility, which is the default.
Format of 'inituser' and 'access'
---------------------------------
A password file should consist of a single line of the form::
name:password
Note that you may also add an optional third component to the line in the
access file to restrict access by domain. For example, the line::
mario:nintendoRules:*.mydomain.com
in your 'access' file will only allow permit emergency user access
from `*.mydomain.com` machines. Attempts to access the system from
other domains will fail, even if the correct emergency user name
and password are used.
Please note that if you use the ZServer monitor capability, you will
need to run with a clear text password.
How to build and install Zope from source code on Windows.
----------------------------------------------------------
* Ensure you have the correct MSVC version installed for the
version of Python you will be using.
* Install (or build from sources) Python
http://www.python.org
* Install (or build from sources) the Python for Windows extensions
http://sourceforge.net/projects/pywin32/
* Unpack the Zope source distribution. Change to that directory.
* Execute:
% python.exe inst\configure.py
It should say something like:
>
> - Zope top-level binary directory will be c:\Zope-2.13.
> - Makefile written.
>
> Next, run the Visual C++ batch file "VCVARS32.bat" and then "nmake".
(run 'configure.py --help' to see how to change things)
* 'makefile' will have ben created. As instructed, execute 'nmake'.
If the build succeeds, the last message printed should be:
> Zope built. Next, do 'nmake install'.
* As instructed, execute 'nmake install'. A few warnings will be generated,
but they can be ignored. The last message in the build process should be:
> Zope binaries installed successfully.
* Zope itself has now been installed. We need to create an instance. Run:
% python.exe {install_path}\bin\mkzopeinstance.py
We will be prompted, via the console, for the instance directory and
username/password for the admin user.
* We are now ready to start zope. Run:
% {zope_instance}\bin\runzope.bat
Zope should start with nice log messages being printed to
stdout. When Zope is ready, you should see:
> ------
> 2004-10-13T12:27:58 INFO(0) Zope Ready to handle requests
Press Ctrl+C to stop this instance of the server.
* Optionally, install as a Windows service. Execute:
% python {zope_instance}\bin\zopeservice.py
to see the valid options. You may want something like:
% python {zope_instance}\bin\zopeservice.py --startup=auto install
Once installed, it can be started any number of ways:
- % {zope_instance}\bin\zopectl.bat start
- % python {zope_instance}\bin\zopeservice.py start
- Control Panel
- % net start service_short_name (eg, `net start Zope_-1227678699`)
...@@ -8,12 +8,11 @@ Contents: ...@@ -8,12 +8,11 @@ Contents:
:maxdepth: 2 :maxdepth: 2
WHATSNEW.rst WHATSNEW.rst
CHANGES.rst
INSTALL.rst INSTALL.rst
USERS.rst
SECURITY.rst SECURITY.rst
ZOPE3.rst
SETUID.rst SETUID.rst
SIGNALS.rst SIGNALS.rst
DEBUGGING.rst DEBUGGING.rst
CREDITS.rst CHANGES.rst
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=..\bin\sphinx-build
set ALLSPHINXOPTS=-d .build/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (.build\*) do rmdir /q /s %%i
del /q /s .build\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% .build/html
echo.
echo.Build finished. The HTML pages are in .build/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% .build/dirhtml
echo.
echo.Build finished. The HTML pages are in .build/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% .build/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% .build/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% .build/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in .build/htmlhelp.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% .build/latex
echo.
echo.Build finished; the LaTeX files are in .build/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% .build/changes
echo.
echo.The overview file is in .build/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% .build/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in .build/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% .build/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in .build/doctest/output.txt.
goto end
)
:end
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Compatibility stub (Zope 2.7's up to b1 used to keep all nt service-related
files here; they've since moved to ntservice_utils)
"""
from nt_svcutils import service
import win32serviceutil
# this is a class which instance services subclass
ZopeService = service.Service
if __name__=='__main__':
win32serviceutil.HandleCommandLine(ZopeService)
""" Placeholder module file """
...@@ -51,10 +51,56 @@ from ZConfig.datatypes import existing_dirpath ...@@ -51,10 +51,56 @@ from ZConfig.datatypes import existing_dirpath
WIN = False WIN = False
if sys.platform[:3].lower() == "win": if sys.platform[:3].lower() == "win":
WIN = True WIN = True
import win32serviceutil
from nt_svcutils import service
def do_windows(command):
def inner(self,arg):
INSTANCE_HOME = self.options.directory
name = 'Zope'+str(hash(INSTANCE_HOME.lower()))
display_name = 'Zope instance at '+INSTANCE_HOME
# This class exists only so we can take advantage of
# win32serviceutil.HandleCommandLine, it is never
# instantiated.
class InstanceService(service.Service):
_svc_name_ = name
_svc_display_name_ = display_name
_svc_description_ = "A Zope application instance running as a service"
# getopt sucks :-(
argv = [sys.argv[0]]
argv.extend(arg.split())
argv.append(command)
# we need to supply this manually as HandleCommandLine guesses wrong
serviceClassName = os.path.splitext(service.__file__)[0]+'.Service'
err = win32serviceutil.HandleCommandLine(
InstanceService,
serviceClassName,
argv=argv,
)
return err,InstanceService
return inner
def string_list(arg): def string_list(arg):
return arg.split() return arg.split()
def quote_command(command):
print " ".join(command)
# Quote the program name, so it works even if it contains spaces
command = " ".join(['"%s"' % x for x in command])
if WIN:
# odd, but true: the windows cmd processor can't handle more than
# one quoted item per string unless you add quotes around the
# whole line.
command = '"%s"' % command
return command
class ZopeCtlOptions(ZDOptions): class ZopeCtlOptions(ZDOptions):
# Zope controller options. # Zope controller options.
# #
...@@ -132,11 +178,6 @@ class ZopeCtlOptions(ZDOptions): ...@@ -132,11 +178,6 @@ class ZopeCtlOptions(ZDOptions):
self.python = os.environ.get('PYTHON', config.python) or sys.executable self.python = os.environ.get('PYTHON', config.python) or sys.executable
self.zdrun = os.path.join(os.path.dirname(zdaemon.__file__), self.zdrun = os.path.join(os.path.dirname(zdaemon.__file__),
"zdrun.py") "zdrun.py")
if WIN:
# Add the path to the zopeservice.py script, which is needed for
# some of the Windows specific commands
servicescript = os.path.join(self.directory, 'bin', 'zopeservice.py')
self.servicescript = '"%s" %s' % (self.python, servicescript)
self.exitcodes = [0, 2] self.exitcodes = [0, 2]
if self.logfile is None and config.eventlog is not None: if self.logfile is None and config.eventlog is not None:
...@@ -171,6 +212,13 @@ class ZopeCmd(ZDCmd): ...@@ -171,6 +212,13 @@ class ZopeCmd(ZDCmd):
args = [opt, svalue] args = [opt, svalue]
return args return args
def do_start(self, arg):
# signal to Zope that it is being managed
# (to indicate it's web-restartable)
os.putenv('ZMANAGED', '1')
## START OF WINDOWS ONLY STUFF
if WIN: if WIN:
def get_status(self): def get_status(self):
# get_status from zdaemon relies on *nix specific socket handling. # get_status from zdaemon relies on *nix specific socket handling.
...@@ -182,52 +230,48 @@ class ZopeCmd(ZDCmd): ...@@ -182,52 +230,48 @@ class ZopeCmd(ZDCmd):
self.zd_status = None self.zd_status = None
return return
def do_stop(self, arg): do_start = do_windows('start')
# Stop the Windows service do_stop = do_windows('stop')
program = "%s stop" % self.options.servicescript do_restart = do_windows('restart')
print program
os.system(program)
def do_restart(self, arg):
# Restart the Windows service
program = "%s restart" % self.options.servicescript
print program
os.system(program)
# Add extra commands to install and remove the Windows service # Add extra commands to install and remove the Windows service
def do_install(self, arg): def do_install(self,arg):
program = "%s install" % self.options.servicescript err,InstanceClass = do_windows('install')(self,arg)
print program if not err:
os.system(program) # If we installed successfully, put info in registry for the
# real Service class to use:
command = '"%s" -C "%s"' % (
# This gives us the instance script for buildout instances
# and the install script for classic instances.
os.path.join(os.path.split(sys.argv[0])[0],'runzope'),
self.options.configfile
)
InstanceClass.setReg('command',command)
# This is unfortunately needed because runzope.exe is a setuptools
# generated .exe that spawns off a sub process, so pid would give us
# the wrong event name.
InstanceClass.setReg('pid_filename',self.options.configroot.pid_filename)
def help_install(self): def help_install(self):
print "install -- Installs Zope as a Windows service." print "install -- Installs Zope as a Windows service."
def do_remove(self, arg): do_remove = do_windows('remove')
program = "%s remove" % self.options.servicescript
print program
os.system(program)
def help_remove(self): def help_remove(self):
print "remove -- Removes the Zope Windows service." print "remove -- Removes the Zope Windows service."
def do_start(self, arg): do_windebug = do_windows('debug')
# signal to Zope that it is being managed
# (to indicate it's web-restartable) def help_windebug(self):
os.putenv('ZMANAGED', '1') print "windebug -- Runs the Zope Windows service in the foreground, in debug mode."
if WIN:
# On Windows start the service, this fails with a reasonable ## END OF WINDOWS ONLY STUFF
# error message as long as the service is not installed
program = "%s start" % self.options.servicescript
print program
os.system(program)
else:
ZDCmd.do_start(self, arg)
def get_startup_cmd(self, python, more): def get_startup_cmd(self, python, more):
cmdline = ( '%s -c "from Zope2 import configure;' cmdline = ( '%s -c "from Zope2 import configure;'
'configure(\'%s\');' % 'configure(%r);' %
(python, self.options.configfile) (python, self.options.configfile)
) )
return cmdline + more + '\"' return cmdline + more + '\"'
...@@ -240,17 +284,18 @@ class ZopeCmd(ZDCmd): ...@@ -240,17 +284,18 @@ class ZopeCmd(ZDCmd):
os.system(cmdline) os.system(cmdline)
def do_foreground(self, arg): def do_foreground(self, arg):
if WIN: program = self.options.program
# Adding arguments to the program is not supported on Windows local_additions = []
# and the runzope script doesn't put you in debug-mode either if not program.count('-X'):
ZDCmd.do_foreground(self, arg) local_additions += ['-X']
else: if not program.count('debug-mode=on'):
self.options.program[1:1] = ["-X", "debug-mode=on"] local_additions += ['debug-mode=on']
program[1:1] = local_additions
command = quote_command(program)
try: try:
ZDCmd.do_foreground(self, arg) return os.system(command)
finally: finally:
self.options.program.remove("-X") for addition in local_additions: program.remove(addition)
self.options.program.remove("debug-mode=on")
def help_debug(self): def help_debug(self):
print "debug -- run the Zope debugger to inspect your database" print "debug -- run the Zope debugger to inspect your database"
......
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
A Zope Windows NT service frontend.
Usage:
Installation
The Zope service should be installed by the Zope Windows
installer. You can manually install, uninstall the service from
the commandline.
ntservice.py [options] install|update|remove|start [...]
|stop|restart [...]|debug [...]
Options for 'install' and 'update' commands only:
--username domain\username : The Username the service is to run
under
--password password : The password for the username
--startup [manual|auto|disabled] : How the service starts,
default = manual
Commands
install : Installs the service
update : Updates the service. Use this if you change any
configuration settings and need the service to be
re-registered.
remove : Removes the service
start : Starts the service, this can also be done from the
services control panel
stop : Stops the service, this can also be done from the
services control panel
restart : Restarts the service
debug : Runs the service in debug mode
You can view the usage options by running this module without any
arguments.
Starting Zope
Start Zope by clicking the 'start' button in the services control
panel. You can set Zope to automatically start at boot time by
choosing 'Auto' startup by clicking the 'statup' button.
Stopping Zope
Stop Zope by clicking the 'stop' button in the services control
panel. You can also stop Zope through the web by going to the
Zope control panel and by clicking 'Shutdown'.
Event logging
Service related events (such as startup, shutdown, or errors executing
the Zope process) are logged to the NT application event log. Use the
event viewer to see these events.
Zope Events are still written to the Zope event logs.
"""
import sys, os
# these are replacements from mkzopeinstance
INSTANCE_HOME = r'<<INSTANCE_HOME>>'
ZOPE_SCRIPTS = r'<<ZOPE_SCRIPTS>>'
ZOPE2PATH = r'<<ZOPE2PATH>>'
ZOPE_RUN = os.path.join(ZOPE_SCRIPTS, 'runzope')
CONFIG_FILE = os.path.join(INSTANCE_HOME, 'etc', 'zope.conf')
PYTHONSERVICE_EXE = os.path.join(ZOPE_SCRIPTS, 'PythonService.exe')
os.environ["INSTANCE_HOME"] = INSTANCE_HOME
# XXX: we need to find nt_svcutils.service
sys.path[0:0] = [ZOPE2PATH]
from nt_svcutils.service import Service
servicename = 'Zope_%s' % str(hash(INSTANCE_HOME.lower()))
class InstanceService(Service):
_svc_name_ = servicename
_svc_display_name_ = 'Zope instance at %s' % INSTANCE_HOME
# _svc_description_ can also be set (but what to say isn't clear!)
# If the exe we expect is not there, let the service framework search
# for it. This will be true for people running from source builds and
# relying on pre-installed pythonservice.exe.
# Note this is only used at install time, not runtime.
if os.path.isfile(PYTHONSERVICE_EXE):
_exe_name_ = PYTHONSERVICE_EXE
process_runner = ZOPE_RUN
process_args = '-C "%s"' % CONFIG_FILE
if __name__ == '__main__':
import win32serviceutil
win32serviceutil.HandleCommandLine(InstanceService)
############################################################################## ##############################################################################
# #
# Copyright (c) 2003 Zope Corporation and Contributors. # Copyright (c) 2003-2009 Zope Corporation and Contributors.
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
...@@ -50,104 +50,37 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -50,104 +50,37 @@ class Service(win32serviceutil.ServiceFramework):
should be created in the instance home. should be created in the instance home.
""" """
# The PythonService model requires that an actual on-disk class declaration
# represent a single service. Thus, the definitions below for the instance
# must be overridden in a subclass in a file within the instance home for
# each instance.
# The values below are just examples.
_svc_name_ = r'Zope-Instance'
_svc_display_name_ = r'Zope instance at C:\Zope-Instance'
process_runner = r'C:\Program Files\Zope-2.7.0-a1\bin\python.exe'
process_args = r'{path_to}\run.py -C {path_to}\zope.conf'
evtlog_name = 'Zope' evtlog_name = 'Zope'
def __init__(self, args): def __init__(self, args):
# We get passed in the service name
self._svc_name_ = args[0]
# ...and from that, we can look up the other needed bits
# from the registry:
self._svc_display_name_ = self.getReg('DisplayName')
self._svc_command_ = self.getReg('command',keyname='PythonClass')
win32serviceutil.ServiceFramework.__init__(self, args) win32serviceutil.ServiceFramework.__init__(self, args)
# Just say "Zope", instead of "Zope_-xxxxx"
try: # Don't use the service name as the event source name:
servicemanager.SetEventSourceName(self.evtlog_name) servicemanager.SetEventSourceName(self.evtlog_name)
except AttributeError:
# old pywin32 - that's ok.
pass
# Create an event which we will use to wait on. # Create an event which we will use to wait on.
# The "service stop" request will set this event. # The "service stop" request will set this event.
# We create it inheritable so we can pass it to the child process, so # We create it inheritable so we can pass it to the child process, so
# it too can act on the stop event. # it too can act on the stop event.
sa = win32security.SECURITY_ATTRIBUTES() sa = win32security.SECURITY_ATTRIBUTES()
sa.bInheritHandle = True sa.bInheritHandle = True
self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None) self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None)
self.redirect_thread = None
def SvcStop(self):
# Before we do anything, tell the SCM we are starting the stop process.
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.onStop()
# Set the stop event - the main loop takes care of termination.
win32event.SetEvent(self.hWaitStop)
# SvcStop only gets triggered when the user explictly stops (or restarts)
# the service. To shut the service down cleanly when Windows is shutting
# down, we also need to hook SvcShutdown.
SvcShutdown = SvcStop
def onStop(self):
# A hook for subclasses to override
pass
def createProcess(self, cmd):
self.start_time = time.time()
return self.createProcessCaptureIO(cmd)
def logmsg(self, event):
# log a service event using servicemanager.LogMsg
try:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
event,
(self._svc_name_,
" (%s)" % self._svc_display_name_))
except win32api.error, details:
# Failed to write a log entry - most likely problem is
# that the event log is full. We don't want this to kill us
try:
print "FAILED to write INFO event", event, ":", details
except IOError:
pass
def _dolog(self, func, msg):
try:
fullmsg = "%s (%s): %s" % \
(self._svc_name_, self._svc_display_name_, msg)
func(fullmsg)
except win32api.error, details:
# Failed to write a log entry - most likely problem is
# that the event log is full. We don't want this to kill us
try:
print "FAILED to write event log entry:", details
print msg
except IOError:
# And if running as a service, its likely our sys.stdout
# is invalid
pass
def info(self, s):
self._dolog(servicemanager.LogInfoMsg, s)
def warning(self, s): ### ServiceFramework methods
self._dolog(servicemanager.LogWarningMsg, s)
def error(self, s):
self._dolog(servicemanager.LogErrorMsg, s)
def SvcDoRun(self): def SvcDoRun(self):
# indicate to Zope that the process is daemon managed (restartable) # indicate to Zope that the process is daemon managed (restartable)
os.environ['ZMANAGED'] = '1' os.environ['ZMANAGED'] = '1'
# XXX the restart behavior is different here than it is for
# zdaemon.zdrun. we should probably do the same thing in both
# places.
# daemon behavior: we want to to restart the process if it # daemon behavior: we want to to restart the process if it
# dies, but if it dies too many times, we need to give up. # dies, but if it dies too many times, we need to give up.
...@@ -165,77 +98,34 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -165,77 +98,34 @@ class Service(win32serviceutil.ServiceFramework):
# the cumulative backoff seconds counter # the cumulative backoff seconds counter
self.backoff_cumulative = 0 self.backoff_cumulative = 0
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
self.logmsg(servicemanager.PYS_SERVICE_STARTED) self.logmsg(servicemanager.PYS_SERVICE_STARTED)
while 1: while 1:
# We pass *this* file and the handle as the first 2 params, then self.hZope, hThread, pid, tid = self.createProcess(self._svc_command_)
# the 'normal' startup args. self.ReportServiceStatus(win32service.SERVICE_RUNNING)
# See the bottom of this script for how that is handled. keep_running = self.run()
cmd = '"%s" %s' % (self.process_runner, self.process_args) if not keep_running:
info = self.createProcess(cmd) # The daemon process has asked to stop
# info is (hProcess, hThread, pid, tid)
self.hZope = info[0] # process handle
# XXX why the test before the log message?
if self.backoff_interval > BACKOFF_INITIAL_INTERVAL:
self.info("created process")
if not (self.run() and self.checkRestart()):
break break
# should we attempt a restart?
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) if not self.checkRestart():
# Stop the child process by opening the special named event. # No, we should not
# We give it 90 seconds to shutdown normally. If that doesn't
# stop things, we give it 30 seconds to do a "fast" shutdown.
# After that, we just knock it on the head.
winver = sys.getwindowsversion()
for sig, timeout in ((signal.SIGINT, 30), (signal.SIGTERM, 10)):
event_name = "Zope-%d-%d" % (info[2], sig)
# sys.getwindowsversion() -> major, minor, build, platform_id, ver_string
# for platform_id, 2==VER_PLATFORM_WIN32_NT
if winver[0] >= 5 and winver[3] == 2:
event_name = "Global\\" + event_name
try:
he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0,
event_name)
except win32event.error, details:
if details[0] == winerror.ERROR_FILE_NOT_FOUND:
# process already dead!
break break
# no other expected error - report it.
self.warning("Failed to open child shutdown event %s"
% (event_name,))
continue
win32event.SetEvent(he)
# It should be shutting down now - wait for termination, reporting
# progress as we go.
for i in range(timeout):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
rc = win32event.WaitForSingleObject(self.hZope, 3000) self.stop(pid)
if rc == win32event.WAIT_OBJECT_0: self.ReportServiceStatus(win32service.SERVICE_STOPPED)
break self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
# Process terminated - no need to try harder.
if rc == win32event.WAIT_OBJECT_0:
break
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) def SvcStop(self):
# If necessary, kill it # Set the stop event - the main loop takes care of termination.
if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE: win32event.SetEvent(self.hWaitStop)
win32api.TerminateProcess(self.hZope, 3)
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
# Wait for the redirect thread - it should have died as the remote # SvcStop only gets triggered when the user explictly stops (or restarts)
# process terminated. # the service. To shut the service down cleanly when Windows is shutting
# As we are shutting down, we do the join with a little more care, # down, we also need to hook SvcShutdown.
# reporting progress as we wait (even though we never will <wink>) SvcShutdown = SvcStop
if self.redirect_thread is not None:
for i in range(5): ### Helper methods
self.redirect_thread.join(1)
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
if not self.redirect_thread.isAlive():
break
else:
self.warning("Redirect thread did not stop!")
self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
def run(self): def run(self):
"""Monitor the daemon process. """Monitor the daemon process.
...@@ -250,24 +140,19 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -250,24 +140,19 @@ class Service(win32serviceutil.ServiceFramework):
0, # bWaitAll 0, # bWaitAll
win32event.INFINITE) win32event.INFINITE)
if rc == win32event.WAIT_OBJECT_0: if rc == win32event.WAIT_OBJECT_0:
# user sent a stop service request # a stop service request was recieved
keep_running = False keep_running = False
elif rc == win32event.WAIT_OBJECT_0 + 1: elif rc == win32event.WAIT_OBJECT_0 + 1:
# user did not send a service stop request, but
# the process died; this may be an error condition # the process died; this may be an error condition
status = win32process.GetExitCodeProcess(self.hZope) status = win32process.GetExitCodeProcess(self.hZope)
# exit status 0 means the user caused a clean shutdown, # exit status 0 means a clean shutdown,
# presumably via the web interface. Any other status # presumably via the web interface.
# is an error that gets written to the event log.
if status != 0:
# This should never block - the child process terminating
# has closed the redirection pipe, so our thread dies.
self.redirect_thread.join(5)
if self.redirect_thread.isAlive():
self.warning("Redirect thread did not stop!")
self.warning("process terminated with exit code %d.\n%s" \
% (status, "".join(self.captured_blocks)))
keep_running = status != 0 keep_running = status != 0
if keep_running:
# Any other status is an error so we write it and
# any output to the event log
self.warning("Process terminated with exit code %d.\n%s" \
% (status, self.getCapturedOutput()))
else: else:
# No other valid return codes. # No other valid return codes.
assert 0, rc assert 0, rc
...@@ -276,10 +161,13 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -276,10 +161,13 @@ class Service(win32serviceutil.ServiceFramework):
def checkRestart(self): def checkRestart(self):
# this was an abormal shutdown. # this was an abormal shutdown.
if self.backoff_cumulative > BACKOFF_MAX: if self.backoff_cumulative > BACKOFF_MAX:
self.error("restarting too frequently; quit") self.error("Attempted restarting more than %s times, aborting."
% BACKOFF_MAX)
return False return False
self.warning("sleep %s to avoid rapid restarts" self.warning(
% self.backoff_interval) "Process died unexpectedly, will attempt restart after %s seconds."
% self.backoff_interval
)
if time.time() - self.start_time > BACKOFF_CLEAR_TIME: if time.time() - self.start_time > BACKOFF_CLEAR_TIME:
self.backoff_interval = BACKOFF_INITIAL_INTERVAL self.backoff_interval = BACKOFF_INITIAL_INTERVAL
self.backoff_cumulative = 0 self.backoff_cumulative = 0
...@@ -292,7 +180,9 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -292,7 +180,9 @@ class Service(win32serviceutil.ServiceFramework):
self.backoff_interval *= 2 self.backoff_interval *= 2
return True return True
def createProcessCaptureIO(self, cmd): def createProcess(self, cmd):
self.start_time = time.time()
hInputRead, hInputWriteTemp = self.newPipe() hInputRead, hInputWriteTemp = self.newPipe()
hOutReadTemp, hOutWrite = self.newPipe() hOutReadTemp, hOutWrite = self.newPipe()
pid = win32api.GetCurrentProcess() pid = win32api.GetCurrentProcess()
...@@ -328,18 +218,19 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -328,18 +218,19 @@ class Service(win32serviceutil.ServiceFramework):
hInputWrite.Close() hInputWrite.Close()
# start a thread collecting output # start a thread collecting output
t = threading.Thread(target=self.redirectCaptureThread, t = threading.Thread(
args = (hOutRead,)) target=self.outputCaptureThread,
args = (hOutRead,)
)
t.start() t.start()
self.redirect_thread = t self.output_thread = t
return info return info
def redirectCaptureThread(self, handle): def outputCaptureThread(self, handle):
# Only one of these running at a time, and handling both stdout and # Only one of these running at a time, and handling both stdout and
# stderr on a single handle. The read data is never referenced until # stderr on a single handle. The read data is never referenced until
# the thread dies - so no need for locks around self.captured_blocks. # the thread dies - so no need for locks around self.captured_blocks.
self.captured_blocks = [] self.captured_blocks = []
#self.info("Redirect thread starting")
while 1: while 1:
try: try:
ec, data = win32file.ReadFile(handle, CHILDCAPTURE_BLOCK_SIZE) ec, data = win32file.ReadFile(handle, CHILDCAPTURE_BLOCK_SIZE)
...@@ -352,7 +243,12 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -352,7 +243,12 @@ class Service(win32serviceutil.ServiceFramework):
self.captured_blocks.append(data) self.captured_blocks.append(data)
del self.captured_blocks[CHILDCAPTURE_MAX_BLOCKS:] del self.captured_blocks[CHILDCAPTURE_MAX_BLOCKS:]
handle.Close() handle.Close()
#self.info("Redirect capture thread terminating")
def getCapturedOutput(self):
self.output_thread.join(5)
if self.output_thread.isAlive():
self.warning("Output capturing thread failed to terminate!")
return "".join(self.captured_blocks)
def newPipe(self): def newPipe(self):
sa = win32security.SECURITY_ATTRIBUTES() sa = win32security.SECURITY_ATTRIBUTES()
...@@ -369,8 +265,134 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -369,8 +265,134 @@ class Service(win32serviceutil.ServiceFramework):
pipe.Close() pipe.Close()
return dup return dup
# Real __main__ bootstrap code is in the instance's service module. def stop(self,pid):
if __name__ == '__main__': # call the method that any subclasses out there may implement:
print "This is a framework module - you don't run it directly." self.onStop()
print "See your installation directory for the service script."
sys.exit(1) winver = sys.getwindowsversion()
# This is unfortunately needed because runzope.exe is a setuptools
# generated .exe that spawns off a sub process, so pid would give us
# the wrong event name.
child_pid = int(
open(self.getReg('pid_filename',keyname='PythonClass')).read()
)
# Stop the child process by sending signals to the special named event.
for sig, timeout in (
(signal.SIGINT, 30), # We give it 90 seconds to shutdown normally.
(signal.SIGTERM, 10) # If that doesn't stop things, we give it 30
# seconds to do a "fast" shutdown.
):
# See the Signals.WinSignalHandler module for
# the source of this event name
event_name = "Zope-%d-%d" % (child_pid,sig)
# sys.getwindowsversion() -> major, minor, build, platform_id, ver_string
# for platform_id, 2==VER_PLATFORM_WIN32_NT
if winver[0] >= 5 and winver[3] == 2:
event_name = "Global\\" + event_name
try:
he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0,
event_name)
except win32event.error, details:
# no other expected error - report it.
self.warning("Failed to open child shutdown event %s"
% (event_name,))
continue
win32event.SetEvent(he)
# It should be shutting down now - wait for termination, reporting
# progress as we go.
for i in range(timeout):
# wait for one second
rc = win32event.WaitForSingleObject(self.hZope, 1000)
if rc == win32event.WAIT_OBJECT_0:
break
# Process terminated - no need to try harder.
if rc == win32event.WAIT_OBJECT_0:
break
if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE:
# None of the signals worked, so kill the process
self.warning(
"Terminating process as it could not be gracefully ended"
)
win32api.TerminateProcess(self.hZope, 3)
output = self.getCapturedOutput()
if output:
self.info("Process terminated with output:\n"+output)
### Overridable subclass methods
def onStop(self):
# A hook for subclasses to override.
# Called just before the service is stopped.
pass
### Registry interaction methods
@classmethod
def openKey(cls,serviceName,keyname=None):
keypath = "System\\CurrentControlSet\\Services\\"+serviceName
if keyname:
keypath += ('\\'+keyname)
return win32api.RegOpenKey(
win32con.HKEY_LOCAL_MACHINE,keypath,0,win32con.KEY_ALL_ACCESS
)
@classmethod
def setReg(cls,name,value,serviceName=None,keyname='PythonClass'):
if not serviceName:
serviceName = cls._svc_name_
key = cls.openKey(serviceName,keyname)
try:
win32api.RegSetValueEx(key, name, 0, win32con.REG_SZ, value)
finally:
win32api.RegCloseKey(key)
def getReg(self,name,keyname=None):
key = self.openKey(self._svc_name_,keyname)
return win32api.RegQueryValueEx(key,name)[0]
### Logging methods
def logmsg(self, event):
# log a service event using servicemanager.LogMsg
try:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
event,
(self._svc_name_,
" (%s)" % self._svc_display_name_))
except win32api.error, details:
# Failed to write a log entry - most likely problem is
# that the event log is full. We don't want this to kill us
try:
print "FAILED to write INFO event", event, ":", details
except IOError:
pass
def _dolog(self, func, msg):
try:
fullmsg = "%s (%s): %s" % \
(self._svc_name_, self._svc_display_name_, msg)
func(fullmsg)
except win32api.error, details:
# Failed to write a log entry - most likely problem is
# that the event log is full. We don't want this to kill us
try:
print "FAILED to write event log entry:", details
print msg
except IOError:
# And if running as a service, its likely our sys.stdout
# is invalid
pass
def info(self, s):
self._dolog(servicemanager.LogInfoMsg, s)
def warning(self, s):
self._dolog(servicemanager.LogWarningMsg, s)
def error(self, s):
self._dolog(servicemanager.LogErrorMsg, s)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment