[Zodb-checkins] CVS: ZODB4/ZEO - runsvr.py:1.1
Jeremy Hylton
jeremy@zope.com
Thu, 5 Dec 2002 14:48:46 -0500
Update of /cvs-repository/ZODB4/ZEO
In directory cvs.zope.org:/tmp/cvs-serv14322/ZEO
Added Files:
runsvr.py
Log Message:
Add runsvr.py, which should have been added as part of the sync.
=== Added File ZODB4/ZEO/runsvr.py ===
#! /usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""Start the ZEO storage server.
Usage: %s [-C URL] [-a ADDRESS] [-f FILENAME] [-h]
Options:
-C/--configuration URL -- configuration file or URL
-a/--address ADDRESS -- server address of the form PORT, HOST:PORT, or PATH
(a PATH must contain at least one "/")
-f/--filename FILENAME -- filename for FileStorage
-h/--help -- print this usage message and exit
Unless -C is specified, -a and -f are required.
"""
# The code here is designed to be reused by other, similar servers.
# For the forseeable future, it must work under Python 2.1 as well as
# 2.2 and above.
# XXX The option parsing infrastructure could be shared with zdaemon.py
import os
import sys
import getopt
import signal
import socket
import zLOG
import ZConfig
import ZConfig.Common
import ZConfig.Storage
class Options:
"""A class to parse and hold the command line options.
Options are represented by various attributes (zeoport etc.).
Positional arguments are represented by the args attribute.
This also has a public usage() method that can be used to report
errors related to the command line.
"""
configuration = None
rootconf = None
args = []
def __init__(self, args=None, progname=None, doc=None):
"""Constructor.
Optional arguments:
args -- the command line arguments, less the program name
(default is sys.argv[1:] at the time of call)
progname -- the program name (default sys.argv[0])
doc -- usage message (default, __main__.__doc__)
"""
if args is None:
args = sys.argv[1:]
if progname is None:
progname = sys.argv[0]
self.progname = progname
if doc is None:
import __main__
doc = __main__.__doc__
if doc and not doc.endswith("\n"):
doc += "\n"
self.doc = doc
try:
self.options, self.args = getopt.getopt(args,
self._short_options,
self._long_options)
except getopt.error, msg:
self.usage(str(msg))
for opt, arg in self.options:
self.handle_option(opt, arg)
self.check_options()
# Default set of options. Subclasses should override.
_short_options = "C:h"
_long_options = ["--configuration=", "--help"]
def handle_option(self, opt, arg):
"""Handle one option. Subclasses should override.
This sets the various instance variables overriding the defaults.
When -h is detected, print the module docstring to stdout and exit(0).
"""
if opt in ("-C", "--configuration"):
self.set_configuration(arg)
if opt in ("-h", "--help"):
self.help()
def set_configuration(self, arg):
self.configuration = arg
def check_options(self):
"""Check options. Subclasses may override.
This can be used to ensure certain options are set, etc.
"""
self.load_configuration()
def load_configuration(self):
if self.rootconf or not self.configuration:
return
self.rootconf = ZConfig.load(self.configuration)
def help(self):
"""Print a long help message (self.doc) to stdout and exit(0).
Occurrences of "%s" in self.doc are replaced by self.progname.
"""
doc = self.doc
if doc.find("%s") > 0:
doc = doc.replace("%s", self.progname)
print doc
sys.exit(0)
def usage(self, msg):
"""Print a brief error message to stderr and exit(2)."""
sys.stderr.write("Error: %s\n" % str(msg))
sys.stderr.write("For help, use %s -h\n" % self.progname)
sys.exit(2)
class ZEOOptions(Options):
hostname = None # A subclass may set this
hostconf = None # <Host> section
zeoconf = None # <ZEO> section
logconf = None # <Log> section
family = None # set by -a; AF_UNIX or AF_INET
address = None # set by -a; string or (host, port)
storages = None # set by -f
_short_options = "a:C:f:h"
_long_options = [
"--address=",
"--configuration=",
"--filename=",
"--help",
]
def handle_option(self, opt, arg):
# Alphabetical order please!
if opt in ("-a", "--address"):
if "/" in arg:
self.family = socket.AF_UNIX
self.address = arg
else:
self.family = socket.AF_INET
if ":" in arg:
host, port = arg.split(":", 1)
else:
host = ""
port = arg
try:
port = int(port)
except: # int() can raise all sorts of errors
self.usage("invalid port number: %r" % port)
self.address = (host, port)
elif opt in ("-f", "--filename"):
from ZODB.FileStorage import FileStorage
if not self.storages:
self.storages = {}
key = str(1 + len(self.storages))
self.storages[key] = (FileStorage, {"file_name": arg})
else:
# Pass it to the base class, for --help/-h
Options.handle_option(self, opt, arg)
def check_options(self):
Options.check_options(self) # Calls load_configuration()
if not self.storages:
self.usage("no storages specified; use -f or -C")
if self.family is None:
self.usage("no server address specified; use -a or -C")
if self.args:
self.usage("positional arguments are not supported")
def load_configuration(self):
Options.load_configuration(self) # Sets self.rootconf
if not self.rootconf:
return
try:
self.hostconf = self.rootconf.getSection("Host")
except ZConfig.Common.ConfigurationConflictingSectionError:
if not self.hostname:
self.hostname = socket.getfqdn()
self.hostconf = self.rootconf.getSection("Host", self.hostname)
if self.hostconf is None:
# If no <Host> section exists, fall back to the root
self.hostconf = self.rootconf
self.zeoconf = self.hostconf.getSection("ZEO")
if self.zeoconf is None:
# If no <ZEO> section exists, fall back to the host (or root)
self.zeoconf = self.hostconf
self.logconf = self.hostconf.getSection("Log")
# Now extract options from various configuration sections
self.load_zeoconf()
self.load_logconf()
self.load_storages()
def load_zeoconf(self):
# Get some option defaults from the configuration
if self.family:
# -a option overrides
return
port = self.zeoconf.getint("server-port")
path = self.zeoconf.get("path")
if port and path:
self.usage(
"Configuration contains conflicting ZEO information:\n"
"Exactly one of 'path' and 'server-port' may be given.")
if port:
host = self.hostconf.get("hostname", "")
self.family = socket.AF_INET
self.address = (host, port)
elif path:
self.family = socket.AF_UNIX
self.address = path
def load_logconf(self):
# Get logging options from conf, unless overridden by environment
if not self.logconf:
return
reinit = 0
if os.getenv("EVENT_LOG_FILE") is None:
if os.getenv("STUPID_LOG_FILE") is None:
path = self.logconf.get("path")
if path is not None:
os.environ["EVENT_LOG_FILE"] = path
os.environ["STUPID_LOG_FILE"] = path
reinit = 1
if os.getenv("EVENT_LOG_SEVERITY") is None:
if os.getenv("STUPID_LOG_SEVERITY") is None:
level = self.logconf.get("level")
if level is not None:
os.environ["EVENT_LOG_SEVERITY"] = level
os.environ["STUPID_LOG_SEVERITY"] = level
reinit = 1
if reinit:
zLOG.initialize()
def load_storages(self):
# Get the storage specifications
if self.storages:
# -f option overrides
return
storagesections = self.zeoconf.getChildSections("Storage")
self.storages = {}
for section in storagesections:
name = section.name
if not name:
name = str(1 + len(self.storages))
if self.storages.has_key(name):
# (Actually, the parser doesn't allow this)
self.usage("duplicate storage name %r" % name)
self.storages[name] = ZConfig.Storage.getStorageInfo(section)
class ZEOServer:
OptionsClass = ZEOOptions
def __init__(self, options=None):
if options is None:
options = self.OptionsClass()
self.options = options
def main(self):
self.check_socket()
self.clear_socket()
try:
self.open_storages()
self.setup_signals()
self.create_server()
self.loop_forever()
finally:
self.close_storages()
self.clear_socket()
def check_socket(self):
if self.can_connect(self.options.family, self.options.address):
self.options.usage("address %s already in use" %
repr(self.options.address))
def can_connect(self, family, address):
s = socket.socket(family, socket.SOCK_STREAM)
try:
s.connect(address)
except socket.error:
return 0
else:
s.close()
return 1
def clear_socket(self):
if isinstance(self.options.address, type("")):
try:
os.unlink(self.options.address)
except os.error:
pass
def open_storages(self):
self.storages = {}
for name, (cls, args) in self.options.storages.items():
info("open storage %r: %s.%s(**%r)" %
(name, cls.__module__, cls.__name__, args))
self.storages[name] = cls(**args)
def setup_signals(self):
"""Set up signal handlers.
The signal handler for SIGFOO is a method handle_sigfoo().
If no handler method is defined for a signal, the signal
action is not changed from its initial value. The handler
method is called without additional arguments.
"""
if os.name != "posix":
return
if hasattr(signal, 'SIGXFSZ'):
signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case
init_signames()
for sig, name in signames.items():
method = getattr(self, "handle_" + name.lower(), None)
if method is not None:
def wrapper(sig_dummy, frame_dummy, method=method):
method()
signal.signal(sig, wrapper)
def create_server(self):
from ZEO.StorageServer import StorageServer
self.server = StorageServer(self.options.address, self.storages)
def loop_forever(self):
import asyncore
asyncore.loop()
def handle_sigterm(self):
info("terminated by SIGTERM")
sys.exit(0)
def handle_sigint(self):
info("terminated by SIGINT")
sys.exit(0)
def handle_sigusr2(self):
# This requires a modern zLOG (from Zope 2.6 or later); older
# zLOG packages don't have the initialize() method
info("reinitializing zLOG")
# XXX Shouldn't this be below with _log()?
import zLOG
zLOG.initialize()
def close_storages(self):
for name, storage in self.storages.items():
info("closing storage %r" % name)
try:
storage.close()
except: # Keep going
exception("failed to close storage %r" % name)
# Signal names
signames = None
def signame(sig):
"""Return a symbolic name for a signal.
Return "signal NNN" if there is no corresponding SIG name in the
signal module.
"""
if signames is None:
init_signames()
return signames.get(sig) or "signal %d" % sig
def init_signames():
global signames
signames = {}
for name, sig in signal.__dict__.items():
k_startswith = getattr(name, "startswith", None)
if k_startswith is None:
continue
if k_startswith("SIG") and not k_startswith("SIG_"):
signames[sig] = name
# Log messages with various severities.
# This uses zLOG, but the API is a simplified version of PEP 282
def critical(msg):
"""Log a critical message."""
_log(msg, zLOG.PANIC)
def error(msg):
"""Log an error message."""
_log(msg, zLOG.ERROR)
def exception(msg):
"""Log an exception (an error message with a traceback attached)."""
_log(msg, zLOG.ERROR, error=sys.exc_info())
def warn(msg):
"""Log a warning message."""
_log(msg, zLOG.PROBLEM)
def info(msg):
"""Log an informational message."""
_log(msg, zLOG.INFO)
def debug(msg):
"""Log a debugging message."""
_log(msg, zLOG.DEBUG)
def _log(msg, severity=zLOG.INFO, error=None):
"""Internal: generic logging function."""
zLOG.LOG("RUNSVR", severity, msg, "", error)
# Main program
def main(args=None):
options = ZEOOptions(args)
s = ZEOServer(options)
s.main()
if __name__ == "__main__":
main()