Browse Source

UNIX domain socket support in server.

Chris McDonough 19 năm trước cách đây
mục cha
commit
240dc65398
3 tập tin đã thay đổi với 191 bổ sung17 xóa
  1. 4 1
      sample.conf
  2. 145 13
      src/supervisor/http.py
  3. 42 3
      src/supervisor/options.py

+ 4 - 1
sample.conf

@@ -1,5 +1,8 @@
 [supervisord]
-xmlrpc_port=127.0.0.1:9001  ; (http port;default is to run no xmlrpc server)
+xmlrpc_port=supervisor.sock ; (default is to run a UNIX domain socket server)
+;xmlrpc_port=127.0.0.1:9001  ; (alternately, ip_address:port specifies AF_INET)
+sockchmod=0700              ; AF_UNIX socket creation mode (AF_INET ignores)
+;sockchown=chrism.chrism     ; AF_UNIX socket uid.gid owner (AF_INET ignores)
 exitcodes=0,2               ; ('expected' exit codes;default 0,2)
 umask=022                   ; (process umask;default 022)
 logfile=supervisord.log     ; (main log file;default $CWD/supervisord.log)

+ 145 - 13
src/supervisor/http.py

@@ -7,6 +7,8 @@ import stat
 import time
 import sys
 import string
+import socket
+import errno
 
 NOT_DONE_YET = []
 
@@ -341,30 +343,148 @@ class deferring_http_channel(http_server.http_channel):
 
 class supervisor_http_server(http_server.http_server):
     channel_class = deferring_http_channel
+    ip = None
 
-    def __init__ (self, ip, port, resolver=None, logger_object=None,
-                  username=None, password=None):
+    def prebind(self, sock, logger_object):
         """ Override __init__ to do logger setup earlier so it can
         go to our logger object instead of stdout """
         from medusa import logger
 
         if not logger_object:
-            logger_object = logger.file_logger (sys.stdout)
+            logger_object = logger.file_logger(sys.stdout)
 
-        if resolver:
-            self.logger = logger.resolving_logger (resolver, logger_object)
-        else:
-            self.logger = logger.unresolving_logger (logger_object)
+        logger_object = logger.unresolving_logger(logger_object)
+        self.logger = logger_object
+
+        asyncore.dispatcher.__init__ (self)
+        self.set_socket(sock)
+
+        self.handlers = []
+
+        sock.setblocking(0)
+        self.set_reuse_addr()
         
-        return http_server.http_server.__init__(self, ip, port, resolver,
-                                                logger_object)
+    def postbind(self):
+        from medusa.counter import counter
+        from medusa.http_server import VERSION_STRING
+
+        self.listen(1024)
+
+        self.total_clients = counter()
+        self.total_requests = counter()
+        self.exceptions = counter()
+        self.bytes_out = counter()
+        self.bytes_in  = counter()
+
+        self.log_info (
+                'Medusa (V%s) started at %s'
+                '\n\tHostname: %s'
+                '\n\tPort:%s'
+                '\n' % (
+                        VERSION_STRING,
+                        time.ctime(time.time()),
+                        self.server_name,
+                        self.port,
+                        )
+                )
 
     def log_info(self, message, type='info'):
         ip = ''
-        if hasattr(self, 'ip'):
+        if getattr(self, 'ip', None) is not None:
             ip = self.ip
         self.logger.log(ip, message)
 
+class supervisor_af_inet_http_server(supervisor_http_server):
+    """ AF_INET version of supervisor HTTP server """
+
+    def __init__(self, ip, port, logger_object):
+        self.ip = ip
+        self.port = port
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.prebind(sock, logger_object)
+        self.bind((ip, port))
+
+        host, port = self.socket.getsockname()
+        if not ip:
+            self.log_info('Computing default hostname', 'warning')
+            ip = socket.gethostbyname (socket.gethostname())
+        try:
+            self.server_name = socket.gethostbyaddr (ip)[0]
+        except socket.error:
+            self.log_info('Cannot do reverse lookup', 'warning')
+            self.server_name = ip       # use the IP address as the "hostname"
+
+        self.postbind()
+
+class supervisor_af_unix_http_server(supervisor_http_server):
+    """ AF_UNIX version of supervisor HTTP server """
+
+    def __init__(self, socketname, sockchmod, sockchown, logger_object):
+        self.ip = socketname
+        self.port = socketname
+
+        # XXX this is insecure.  We really should do something like
+        # http://developer.apple.com/samplecode/CFLocalServer/listing6.html
+        # (see also http://developer.apple.com/technotes/tn2005/tn2083.html#SECUNIXDOMAINSOCKETS)
+        # but it would be very inconvenient for the user to need to get all
+        # the directory setup right.
+
+        tempname = "%s.%d" % (socketname, os.getpid())
+
+        try:
+            os.unlink(tempname)
+        except os.error:
+            pass
+
+        while 1:
+            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            try:
+                sock.bind(tempname)
+                os.chmod(tempname, sockchmod)
+                try:
+                    os.link(tempname, socketname)
+                except os.error:
+                    # Lock contention, or stale socket.
+                    used = self.checkused(socketname)
+                    if used:
+                        # cooperate with 'openhttpserver' in supervisord
+                        raise socket.error(errno.EADDRINUSE)
+                        
+                    # Stale socket -- delete, sleep, and try again.
+                    msg = "Unlinking stale socket %s\n" % socketname
+                    sys.stderr.write(msg)
+                    try:
+                        os.unlink(socketname)
+                    except:
+                        pass
+                    sock.close()
+                    time.sleep(.3)
+                    continue
+                else:
+                    os.chown(socketname, sockchown[0], sockchown[1])
+                    self.prebind(sock, logger_object)
+                    break
+
+            finally:
+                try:
+                    os.unlink(tempname)
+                except os.error:
+                    pass
+
+        self.server_name = '<unix domain socket>'
+        self.postbind()
+
+    def checkused(self, socketname):
+        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        try:
+            s.connect(socketname)
+            s.send("HTTP/1.0 GET /\n\n")
+            data = s.recv(1)
+            s.close()
+        except socket.error:
+            return False
+        else:
+            return True
 
 class tail_f_producer:
     def __init__(self, request, filename, head):
@@ -453,7 +573,6 @@ def makeHTTPServer(supervisord):
     if not options.xmlrpc_port:
         return
 
-    host, port = options.xmlrpc_port.address
     username = options.xmlrpc_username
     password = options.xmlrpc_password
 
@@ -463,8 +582,21 @@ def makeHTTPServer(supervisord):
                 msg = msg[:-1]
             options.logger.debug(msg)
     wrapper = LogWrapper()
-    hs = supervisor_http_server(host, port, logger_object=wrapper,
-                                username=username, password=password)
+
+    family = options.xmlrpc_port.family
+    
+    if family == socket.AF_INET:
+        host, port = options.xmlrpc_port.address
+        hs = supervisor_af_inet_http_server(host, port, logger_object=wrapper)
+    elif family == socket.AF_UNIX:
+        socketname = options.xmlrpc_port.address
+        sockchmod = options.sockchmod
+        sockchown = options.sockchown
+        hs = supervisor_af_unix_http_server(socketname, sockchmod, sockchown,
+                                            logger_object=wrapper)
+    else:
+        raise ValueError('Cannot determine socket type %r' % family)
+
     from rpc import supervisor_xmlrpc_handler
     xmlrpchandler = supervisor_xmlrpc_handler(supervisord)
     tailhandler = logtail_handler(supervisord)

+ 42 - 3
src/supervisor/options.py

@@ -502,6 +502,10 @@ class ServerOptions(Options):
                  "", "minprocs=", int, default=200)
         self.add("nocleanup", "supervisord.nocleanup",
                  "k", "nocleanup", flag=1, default=0)
+	self.add("sockchmod", "supervisord.sockchmod", "p:", "socket-mode=",
+		 datatypes.octal_type, default=0700)
+	self.add("sockchown", "supervisord.sockchown", "o:", "socket-owner=",
+		 datatypes.dot_separated_user_group)
 
     def getLogger(self, filename, level, fmt, rotating=False,
                   maxbytes=0, backups=0):
@@ -540,6 +544,7 @@ class ServerOptions(Options):
             logfile = os.path.abspath(self.configroot.supervisord.logfile)
         else:
             logfile = os.path.abspath(self.logfile)
+
         self.logfile = logfile
 
         if not self.loglevel:
@@ -550,16 +555,31 @@ class ServerOptions(Options):
         else:
             self.pidfile = os.path.abspath(self.pidfile)
 
-        self.noauth = True
-        self.passwdfile = None
-
         self.programs = self.configroot.supervisord.programs
 
+        if not self.sockchown:
+            self.sockchown = self.configroot.supervisord.sockchown
+
         self.identifier = self.configroot.supervisord.identifier
 
         if self.nodaemon:
             self.daemon = False
 
+    def convert_sockchown(self, sockchown):
+        # Convert chown stuff to uid/gid
+        user = sockchown[0]
+        group = sockchown[1]
+        uid = datatypes.name_to_uid(user)
+        if uid is None:
+            self.usage("No such sockchown user %s" % user)
+        if group is None:
+            gid = datatypes.gid_for_uid(uid)
+        else:
+            gid = datatypes.name_to_gid(group)
+            if gid is None:
+                self.usage("No such sockchown group %s" % group)
+        return uid, gid
+
     def read_config(self, fp):
         section = self.configroot.supervisord
         if not hasattr(fp, 'read'):
@@ -656,6 +676,25 @@ class ServerOptions(Options):
         nocleanup = config.getdefault('nocleanup', 'false')
         section.nocleanup = datatypes.boolean(nocleanup)
 
+        sockchown = config.getdefault('sockchown', None)
+        if sockchown is None:
+            section.sockchown = (-1, -1)
+        else:
+            try:
+                section.sockchown = datatypes.dot_separated_user_group(
+                    sockchown)
+            except ValueError:
+                raise ValueError('Invalid sockchown value %s' % sockchown)
+
+        sockchmod = config.getdefault('sockchmod', None)
+        if sockchmod is None:
+            section.sockchmod = 0700
+        else:
+            try:
+                section.sockchmod = datatypes.octal_type(sockchmod)
+            except (TypeError, ValueError):
+                raise ValueError('Invalid sockchmod value %s' % sockchmod)
+
         section.programs = self.programs_from_config(config)
         return section