Commit 49a72738 authored by Chris Withers's avatar Chris Withers

- rework Windows service stuff to make zopeservice.py in the instance home un-necessary

  (this means that buildout instances work on Windows too :-) )
- remove a few stray comments and old unneeded code from nt_svcutils/service.py
parent ba393ec3
...@@ -51,6 +51,41 @@ from ZConfig.datatypes import existing_dirpath ...@@ -51,6 +51,41 @@ 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()
...@@ -132,11 +167,6 @@ class ZopeCtlOptions(ZDOptions): ...@@ -132,11 +167,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 +201,8 @@ class ZopeCmd(ZDCmd): ...@@ -171,6 +201,8 @@ class ZopeCmd(ZDCmd):
args = [opt, svalue] args = [opt, svalue]
return args return args
## 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,46 +214,40 @@ class ZopeCmd(ZDCmd): ...@@ -182,46 +214,40 @@ class ZopeCmd(ZDCmd):
self.zd_status = None self.zd_status = None
return return
def do_stop(self, arg): do_stop = do_windows('stop')
# Stop the Windows service do_restart = do_windows('restart')
program = "%s stop" % self.options.servicescript
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)
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."
## END OF WINDOWS ONLY STUFF
def do_start(self, arg): def do_start(self, arg):
# signal to Zope that it is being managed # signal to Zope that it is being managed
# (to indicate it's web-restartable) # (to indicate it's web-restartable)
os.putenv('ZMANAGED', '1') os.putenv('ZMANAGED', '1')
if WIN: if WIN:
# On Windows start the service, this fails with a reasonable do_windows('start')(self,arg)
# error message as long as the service is not installed
program = "%s start" % self.options.servicescript
print program
os.system(program)
else: else:
ZDCmd.do_start(self, arg) ZDCmd.do_start(self, arg)
......
##############################################################################
#
# 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,26 +50,21 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -50,26 +50,21 @@ 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: 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
...@@ -80,6 +75,27 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -80,6 +75,27 @@ class Service(win32serviceutil.ServiceFramework):
self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None) self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None)
self.redirect_thread = None self.redirect_thread = None
@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]
def SvcStop(self): def SvcStop(self):
# Before we do anything, tell the SCM we are starting the stop process. # Before we do anything, tell the SCM we are starting the stop process.
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
...@@ -168,15 +184,12 @@ class Service(win32serviceutil.ServiceFramework): ...@@ -168,15 +184,12 @@ class Service(win32serviceutil.ServiceFramework):
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 info = self.createProcess(self._svc_command_)
# the 'normal' startup args.
# See the bottom of this script for how that is handled.
cmd = '"%s" %s' % (self.process_runner, self.process_args)
info = self.createProcess(cmd)
# info is (hProcess, hThread, pid, tid) # info is (hProcess, hThread, pid, tid)
self.hZope = info[0] # process handle self.hZope = info[0] # process handle
# XXX why the test before the log message?
if self.backoff_interval > BACKOFF_INITIAL_INTERVAL: if self.backoff_interval > BACKOFF_INITIAL_INTERVAL:
# make a note that we've created a process after backing
# off?
self.info("created process") self.info("created process")
if not (self.run() and self.checkRestart()): if not (self.run() and self.checkRestart()):
break break
......
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