This patch completes the transition to running under mod_wsgi.  It
requires my previous "049 Consolidate to single WSGI entry point" patch.

This is pretty strait forward, but a few things need highlighting:

1. mod_wsgi requires an entry point script (you can't give it a Python
package name like we were doing with mod_python).  Based on my reading
of the Filesystem Hierarchy Standard, it seems this should be in
share/ipa, so that's what I did.  The script is /usr/share/ipa/wsgi.py
I was expecting this to cause SELinux problems, but things seem to work
fine.

2. We are running mod_wsgi in daemon mode, which is the preferred way of
deploying it.  The mod_wsgi daemon has both multi-process and
multi-threading capabilities.  As we haven't actually used threaded code
much in IPA thus far (although lite-server.py is threaded), for now I
have the daemon running 2 processes and 1 thread (aka it's not
threaded).  For production I think we probably should run something like
4 processes and 8 threads per process.  This can be a later change (just
requires a change in our ipa.conf Apache config file).

3. As ipaserver is now running inside the mod_wsgi daemon, we can
changed from using the Apache "prefork" MPM to using "worker", which is
far superior for static content.  I haven't changed this yet, but we
should put this on our TODO.

I pretty much had this patch all done last Friday, but I've let things
slow-roast for several days to make sure it's stable.  I feel confident
that this is a low risk change.  All the same, I think we should get
this pushed as soon as possible so we can shake out any remaining
issues.
>From dca4ee9920b8e9f323847486a5e80b0168d87b8a Mon Sep 17 00:00:00 2001
From: Jason Gerard DeRose <jder...@redhat.com>
Date: Wed, 24 Feb 2010 11:29:23 -0700
Subject: [PATCH] Run ipaserver under mod_wsgi

---
 install/conf/ipa.conf     |  103 +++++++++--------------
 install/share/Makefile.am |    1 +
 install/share/wsgi.py     |   13 +++
 ipa.spec.in               |    5 +
 ipaserver/__init__.py     |  206 ---------------------------------------------
 5 files changed, 59 insertions(+), 269 deletions(-)
 create mode 100644 install/share/wsgi.py

diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index f5987fb..dba47c5 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -4,7 +4,6 @@
 # LoadModule auth_kerb_module modules/mod_auth_kerb.so
 
 ProxyRequests Off
-PythonImport ipaserver main_interpreter
 
 # ipa-rewrite.conf is loaded separately
 
@@ -12,79 +11,47 @@ PythonImport ipaserver main_interpreter
 AddType application/java-archive        jar
 
 
+# FIXME: WSGISocketPrefix is a server-scope directive.  The mod_wsgi package
+# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
+WSGISocketPrefix /var/run/httpd/wsgi
 
-<Location "/ipa">
-  AuthType Kerberos
-  AuthName "Kerberos Login"
-  KrbMethodNegotiate on
-  KrbMethodK5Passwd off
-  KrbServiceName HTTP
-  KrbAuthRealms $REALM
-  Krb5KeyTab /etc/httpd/conf/ipa.keytab
-  KrbSaveCredentials on
-  Require valid-user
-  ErrorDocument 401 /ipa/errors/unauthorized.html
-
-  SetHandler python-program
-  PythonInterpreter main_interpreter
-  PythonHandler ipaserver::handler
-  PythonDebug Off
-  PythonOption SCRIPT_NAME /ipa
-  PythonAutoReload Off
-
-</Location>
-
-#<Location "/ipa/xml">
-#  SetHandler python-program
-#  PythonInterpreter main_interpreter
-#  PythonHandler ipaserver::xmlrpc
-#  PythonDebug Off
-#  PythonOption SCRIPT_NAME /ipa/xml
-#  PythonAutoReload Off
-#</Location>
-
-#<Location "/ipa/json">
-#  SetHandler python-program
-#  PythonInterpreter main_interpreter
-#  PythonHandler ipaserver::jsonrpc
-#  PythonDebug Off
-#  PythonOption SCRIPT_NAME /ipa/json
-#  PythonAutoReload Off
-#</Location>
-
-#<Location "/ipa/ui">
-#  SetHandler python-program
-#  PythonInterpreter main_interpreter
-#  PythonHandler ipaserver::webui
-#  PythonDebug Off
-#  PythonOption SCRIPT_NAME /ipa/ui
-#  PythonAutoReload Off
-#</Location>
 
-Alias /ipa-assets/ "/var/cache/ipa/assets/"
-<Directory "/var/cache/ipa/assets">
-  Allow from all
-  AllowOverride None
-  # add Indexes to Options to allow browsing
-  Options FollowSymLinks
-  ExpiresActive On
-  ExpiresDefault A31536000
-</Directory>
+# Configure mod_wsgi handler for /ipa
+WSGIDaemonProcess ipa processes=2 threads=1 maximum-requests=500
+WSGIProcessGroup ipa
+WSGIApplicationGroup ipa
+WSGIImportScript /usr/share/ipa/wsgi.py process-group=ipa application-group=ipa
+WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py
+WSGIScriptReloading Off
 
 
+# Turn off mod_msgi handler for errors, config, crl:
 <Location "/ipa/errors">
   SetHandler None
 </Location>
-
 <Location "/ipa/config">
   SetHandler None
 </Location>
-
 <Location "/ipa/crl">
   SetHandler None
 </Location>
 
 
+# Protect /ipa with Kerberos
+<Location "/ipa">
+  AuthType Kerberos
+  AuthName "Kerberos Login"
+  KrbMethodNegotiate on
+  KrbMethodK5Passwd off
+  KrbServiceName HTTP
+  KrbAuthRealms $REALM
+  Krb5KeyTab /etc/httpd/conf/ipa.keytab
+  KrbSaveCredentials on
+  Require valid-user
+  ErrorDocument 401 /ipa/errors/unauthorized.html
+</Location>
+
+
 # This is where we redirect on failed auth
 Alias /ipa/errors "/usr/share/ipa/html"
 
@@ -102,7 +69,6 @@ Alias /ipa/config "/usr/share/ipa/html"
 
 # For CRL publishing
 Alias /ipa/crl "/var/lib/pki-ca/publish"
-
 <Directory "/var/lib/pki-ca/publish">
   SetHandler None
   AllowOverride None
@@ -111,6 +77,18 @@ Alias /ipa/crl "/var/lib/pki-ca/publish"
   Allow from all
 </Directory>
 
+
+# WebUI assets
+Alias /ipa-assets/ "/var/cache/ipa/assets/"
+<Directory "/var/cache/ipa/assets">
+  Allow from all
+  AllowOverride None
+  Options FollowSymLinks
+  ExpiresActive On
+  ExpiresDefault A31536000
+</Directory>
+
+
 # Protect our CGIs
 <Directory /var/www/cgi-bin>
   AuthType Kerberos
@@ -125,20 +103,19 @@ Alias /ipa/crl "/var/lib/pki-ca/publish"
   ErrorDocument 401 /ipa/errors/unauthorized.html
 </Directory>
 
+
 # migration related pages
 Alias /ipa/migration "/usr/share/ipa/migration"
-
 <Directory "/usr/share/ipa/migration">
     AllowOverride None
     Satisfy Any
     Allow from all
-
     AddHandler mod_python .py
     PythonHandler mod_python.publisher
 </Directory>
 
-#Alias /ipatest "/usr/share/ipa/ipatest"
 
+#Alias /ipatest "/usr/share/ipa/ipatest"
 #<Directory "/usr/share/ipa/ipatest">
 #  AuthType Kerberos
 #  AuthName "Kerberos Login"
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index e3e7cf6..92d5077 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -38,6 +38,7 @@ app_DATA =				\
 	unique-attributes.ldif		\
 	schema_compat.uldif		\
 	ldapi.ldif			\
+	wsgi.py				\
 	$(NULL)
 
 EXTRA_DIST =				\
diff --git a/install/share/wsgi.py b/install/share/wsgi.py
new file mode 100644
index 0000000..457d8e0
--- /dev/null
+++ b/install/share/wsgi.py
@@ -0,0 +1,13 @@
+"""
+WSGI appliction for IPA server.
+"""
+
+from ipalib import api
+api.bootstrap(context='server', debug=True, log=None)
+api.finalize()
+api.log.info('*** PROCESS START ***')
+import ipawebui
+ui = ipawebui.create_wsgi_app(api)
+
+# This is the WSGI callable:
+application = api.Backend.session
diff --git a/ipa.spec.in b/ipa.spec.in
index 0607dd7..f7f3a29 100644
--- a/ipa.spec.in
+++ b/ipa.spec.in
@@ -75,6 +75,7 @@ Requires: cyrus-sasl-gssapi
 Requires: ntp
 Requires: httpd
 Requires: mod_python
+Requires: mod_wsgi
 Requires: mod_auth_kerb
 %{?fc8:Requires: mod_nss >= 1.0.7-2}
 %{?fc9:Requires: mod_nss >= 1.0.7-5}
@@ -383,6 +384,7 @@ fi
 %{python_sitelib}/ipaserver/*
 %{python_sitelib}/ipawebui/*
 %dir %{_usr}/share/ipa
+%{_usr}/share/ipa/wsgi.py
 %{_usr}/share/ipa/*.ldif
 %{_usr}/share/ipa/*.uldif
 %{_usr}/share/ipa/*.template
@@ -497,6 +499,9 @@ fi
 %endif
 
 %changelog
+* Wed Feb 24 2010 Jason Gerard DeRose <jder...@redhat.com> - 1.99-17
+- Added Require mod_wsgi, added share/ipa/wsgi.py
+
 * Thu Feb 11 2010 Jason Gerard DeRose <jder...@redhat.com> - 1.99-16
 - Require python-wehjit >= 0.2.2
 
diff --git a/ipaserver/__init__.py b/ipaserver/__init__.py
index 874ac3e..b0be96b 100644
--- a/ipaserver/__init__.py
+++ b/ipaserver/__init__.py
@@ -20,209 +20,3 @@
 """
 Package containing server backend.
 """
-
-import traceback
-from xmlrpclib import dumps, Fault
-from ipalib import api
-
-
-# This is a simple way to ensure that ipalib.api is only initialized
-# when ipaserver is imported from within the Apache process:
-try:
-    from mod_python import apache
-    api.bootstrap(context='server', debug=True, log=None)
-    api.finalize()
-    api.log.info('*** PROCESS START ***')
-    import ipawebui
-    ui = ipawebui.create_wsgi_app(api)
-except ImportError:
-    pass
-
-
-# START code from paste
-# Red Hat does not hold the copyright to the following code.  The following code
-# is from paste:
-#   http://pythonpaste.org/
-# Which in turn was based on Robert Brewer's modpython_gateway:
-#   http://projects.amor.org/misc/svn/modpython_gateway.py
-
-class InputWrapper(object):
-
-    def __init__(self, req):
-        self.req = req
-
-    def close(self):
-        pass
-
-    def read(self, size=-1):
-        return self.req.read(size)
-
-    def readline(self, size=-1):
-        return self.req.readline(size)
-
-    def readlines(self, hint=-1):
-        return self.req.readlines(hint)
-
-    def __iter__(self):
-        line = self.readline()
-        while line:
-            yield line
-            # Notice this won't prefetch the next line; it only
-            # gets called if the generator is resumed.
-            line = self.readline()
-
-
-class ErrorWrapper(object):
-
-    def __init__(self, req):
-        self.req = req
-
-    def flush(self):
-        pass
-
-    def write(self, msg):
-        self.req.log_error(msg)
-
-    def writelines(self, seq):
-        self.write(''.join(seq))
-
-
-bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
-             "when running a version of mod_python < 3.1")
-
-
-class Handler(object):
-
-    def __init__(self, req):
-        self.started = False
-
-        options = req.get_options()
-
-        # Threading and forking
-        try:
-            q = apache.mpm_query
-            threaded = q(apache.AP_MPMQ_IS_THREADED)
-            forked = q(apache.AP_MPMQ_IS_FORKED)
-        except AttributeError:
-            threaded = options.get('multithread', '').lower()
-            if threaded == 'on':
-                threaded = True
-            elif threaded == 'off':
-                threaded = False
-            else:
-                raise ValueError(bad_value % "multithread")
-
-            forked = options.get('multiprocess', '').lower()
-            if forked == 'on':
-                forked = True
-            elif forked == 'off':
-                forked = False
-            else:
-                raise ValueError(bad_value % "multiprocess")
-
-        env = self.environ = dict(apache.build_cgi_env(req))
-
-        if 'SCRIPT_NAME' in options:
-            # Override SCRIPT_NAME and PATH_INFO if requested.
-            env['SCRIPT_NAME'] = options['SCRIPT_NAME']
-            env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):]
-        else:
-            env['SCRIPT_NAME'] = ''
-            env['PATH_INFO'] = req.uri
-
-        env['wsgi.input'] = InputWrapper(req)
-        env['wsgi.errors'] = ErrorWrapper(req)
-        env['wsgi.version'] = (1, 0)
-        env['wsgi.run_once'] = False
-        if env.get("HTTPS") in ('yes', 'on', '1'):
-            env['wsgi.url_scheme'] = 'https'
-        else:
-            env['wsgi.url_scheme'] = 'http'
-        env['wsgi.multithread']  = threaded
-        env['wsgi.multiprocess'] = forked
-
-        self.request = req
-
-    def run(self, application):
-        try:
-            result = application(self.environ, self.start_response)
-            for data in result:
-                self.write(data)
-            if not self.started:
-                self.request.set_content_length(0)
-            if hasattr(result, 'close'):
-                result.close()
-        except:
-            traceback.print_exc(None, self.environ['wsgi.errors'])
-            if not self.started:
-                self.request.status = 500
-                self.request.content_type = 'text/plain'
-                data = "A server error occurred. Please contact the administrator."
-                self.request.set_content_length(len(data))
-                self.request.write(data)
-
-    def start_response(self, status, headers, exc_info=None):
-        if exc_info:
-            try:
-                if self.started:
-                    raise exc_info[0], exc_info[1], exc_info[2]
-            finally:
-                exc_info = None
-
-        self.request.status = int(status[:3])
-
-        for key, val in headers:
-            if key.lower() == 'content-length':
-                self.request.set_content_length(int(val))
-            elif key.lower() == 'content-type':
-                self.request.content_type = val
-            else:
-                self.request.headers_out.add(key, val)
-
-        return self.write
-
-    def write(self, data):
-        if not self.started:
-            self.started = True
-        self.request.write(data)
-
-# END code from paste
-
-
-def adapter(req, app):
-    if apache.mpm_query(apache.AP_MPMQ_IS_THREADED):
-        response = dumps(
-            Fault(3, 'Apache must use the forked model'),
-            methodresponse=True,
-        )
-        req.content_type = 'text/xml'
-        req.set_content_length(len(response))
-        req.write(response)
-    else:
-        Handler(req).run(app)
-    return apache.OK
-
-
-def xmlrpc(req):
-    """
-    mod_python handler for XML-RPC requests.
-    """
-    return adapter(req, api.Backend.xmlserver)
-
-
-def jsonrpc(req):
-    """
-    mod_python handler for JSON-RPC requests (place holder).
-    """
-    return adapter(req, api.Backend.jsonserver)
-
-
-def webui(req):
-    """
-    mod_python handler for web-UI requests (place holder).
-    """
-    return adapter(req, ui)
-
-
-def handler(req):
-    return adapter(req, api.Backend.session)
-- 
1.6.3.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to