runzeo.py 9.51 KB
Newer Older
1
#!python
2 3
##############################################################################
#
4
# Copyright (c) 2001, 2002, 2003 Zope Corporation and Contributors.
5 6 7 8 9 10 11 12 13 14 15 16
# 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.

17
Usage: %s [-C URL] [-a ADDRESS] [-f FILENAME] [-h]
18 19

Options:
20
-C/--configuration URL -- configuration file or URL
21 22 23
-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
24
-t/--timeout TIMEOUT -- transaction timeout in secondes (default no timeout)
25
-h/--help -- print this usage message and exit
26
-m/--monitor ADDRESS -- address of monitor server ([HOST:]PORT or PATH)
27

28
Unless -C is specified, -a and -f are required.
29 30 31 32 33 34 35 36 37 38 39 40
"""

# 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.

import os
import sys
import getopt
import signal
import socket

41
import ZConfig, ZConfig.datatypes
42
import zLOG
43
import ZEO
44
from zdaemon.zdoptions import ZDOptions
45

46
def parse_address(arg):
Fred Drake's avatar
Fred Drake committed
47
    # XXX Not part of the official ZConfig API
48 49 50
    obj = ZConfig.datatypes.SocketAddress(arg)
    return obj.family, obj.address

51
class ZEOOptionsMixin:
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

    storages = None

    def handle_address(self, arg):
        self.family, self.address = parse_address(arg)

    def handle_monitor_address(self, arg):
        self.monitor_family, self.monitor_address = parse_address(arg)

    def handle_filename(self, arg):
        from ZODB.config import FileStorage # That's a FileStorage *opener*!
        class FSConfig:
            def __init__(self, name, path):
                self._name = name
                self.path = path
                self.create = 0
                self.read_only = 0
                self.stop = None
                self.quota = None
            def getSectionName(self):
                return self._name
73
        if not self.storages:
74 75 76 77 78
            self.storages = []
        name = str(1 + len(self.storages))
        conf = FileStorage(FSConfig(name, arg))
        self.storages.append(conf)

79
    def add_zeo_options(self):
80 81 82 83 84 85 86 87
        self.add(None, None, "a:", "address=", self.handle_address)
        self.add(None, None, "f:", "filename=", self.handle_filename)
        self.add("family", "zeo.address.family")
        self.add("address", "zeo.address.address",
                 required="no server address specified; use -a or -C")
        self.add("read_only", "zeo.read_only", default=0)
        self.add("invalidation_queue_size", "zeo.invalidation_queue_size",
                 default=100)
88 89
        self.add("transaction_timeout", "zeo.transaction_timeout",
                 "t:", "timeout=", float)
90
        self.add("monitor_address", "zeo.monitor_address", "m:", "monitor=",
91
                 self.handle_monitor_address)
92 93 94 95 96 97
        self.add('auth_protocol', 'zeo.authentication_protocol',
                 None, 'auth-protocol=', default=None)
        self.add('auth_database', 'zeo.authentication_database',
                 None, 'auth-database=')
        self.add('auth_realm', 'zeo.authentication_realm',
                 None, 'auth-realm=')
98 99 100

class ZEOOptions(ZDOptions, ZEOOptionsMixin):

101
    logsectionname = "eventlog"
102

103
    def __init__(self):
104
        self.schemadir = os.path.dirname(ZEO.__file__)
105 106 107 108 109
        ZDOptions.__init__(self)
        self.add_zeo_options()
        self.add("storages", "storages",
                 required="no storages specified; use -f or -C")

110

Guido van Rossum's avatar
Guido van Rossum committed
111
class ZEOServer:
112

113
    def __init__(self, options):
Guido van Rossum's avatar
Guido van Rossum committed
114
        self.options = options
115 116

    def main(self):
117
        self.setup_default_logging()
118 119 120
        self.check_socket()
        self.clear_socket()
        try:
121 122
            self.open_storages()
            self.setup_signals()
123 124 125
            self.create_server()
            self.loop_forever()
        finally:
126
            self.close_storages()
127 128
            self.clear_socket()

129 130 131 132 133 134 135 136 137 138 139
    def setup_default_logging(self):
        if self.options.config_logger is not None:
            return
        if os.getenv("EVENT_LOG_FILE") is not None:
            return
        if os.getenv("STUPID_LOG_FILE") is not None:
            return
        # No log file is configured; default to stderr.  The logging
        # level can still be controlled by {STUPID,EVENT}_LOG_SEVERITY.
        os.environ["EVENT_LOG_FILE"] = ""

140
    def check_socket(self):
Guido van Rossum's avatar
Guido van Rossum committed
141 142 143
        if self.can_connect(self.options.family, self.options.address):
            self.options.usage("address %s already in use" %
                               repr(self.options.address))
144 145 146

    def can_connect(self, family, address):
        s = socket.socket(family, socket.SOCK_STREAM)
147
        try:
148
            s.connect(address)
149
        except socket.error:
150
            return 0
151 152
        else:
            s.close()
153
            return 1
154 155

    def clear_socket(self):
Guido van Rossum's avatar
Guido van Rossum committed
156
        if isinstance(self.options.address, type("")):
157
            try:
Guido van Rossum's avatar
Guido van Rossum committed
158
                os.unlink(self.options.address)
159 160 161 162 163
            except os.error:
                pass

    def open_storages(self):
        self.storages = {}
164
        for opener in self.options.storages:
165
            info("opening storage %r using %s"
166 167
                 % (opener.name, opener.__class__.__name__))
            self.storages[opener.name] = opener.open()
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    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
191 192 193 194 195
        self.server = StorageServer(
            self.options.address,
            self.storages,
            read_only=self.options.read_only,
            invalidation_queue_size=self.options.invalidation_queue_size,
196
            transaction_timeout=self.options.transaction_timeout,
197 198
            monitor_address=self.options.monitor_address,
            auth_protocol=self.options.auth_protocol,
Jeremy Hylton's avatar
Jeremy Hylton committed
199
            auth_database=self.options.auth_database,  # XXX option spelling
200
            auth_realm=self.options.auth_realm)
201 202

    def loop_forever(self):
203 204
        import ThreadedAsync.LoopCallback
        ThreadedAsync.LoopCallback.loop()
205 206 207 208 209 210 211 212 213

    def handle_sigterm(self):
        info("terminated by SIGTERM")
        sys.exit(0)

    def handle_sigint(self):
        info("terminated by SIGINT")
        sys.exit(0)

214 215 216 217
    def handle_sighup(self):
        info("restarted by SIGHUP")
        sys.exit(1)

218 219 220 221 222 223 224
    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()
225
        info("reinitialized zLOG")
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

    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):
297 298
    options = ZEOOptions()
    options.realize(args)
Guido van Rossum's avatar
Guido van Rossum committed
299
    s = ZEOServer(options)
300 301 302 303
    s.main()

if __name__ == "__main__":
    main()