Okay, the solution I am going to describe uses Apache/mod_wsgi. This
is an Apache module specifically designed for hosting Python WSGI
applications within Apache. The mod_wsgi module provides two modes of
operations. The first is embedded mode, which works similar to
mod_python in that applications run within the actual Apache child
worker processes. The second mode is daemon mode, which is similar in
some respects to fastcgi solutions, with applications running in
distinct processes from Apache child worker processes, and with Apache
child worker processes merely acting as a proxy to the WSGI
application daemon processes. We will be using mod_wsgi daemon mode in
this case, as it will allow for each Django instance to run in a
separate process.
How one would normally set up Apache/mod_wsgi for Django is described
at:
http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango
What we will be doing here goes beyond that, but please ensure you
read that first and perhaps get a single Django instance running that
way before contemplating the multiplexing arrangement described here.
Getting back to what you wanted, you wanted multiple users to see a
Django instance mounted at same URL, but for each to actually get a
distinct instance associated with a distinct database.
For this, as would normally be done, still use WSGIScriptAlias to
mount the WSGI script file at the appropriate URL. Here we assume it
will be root of web server. Thus:
WSGIScriptAlias / /usr/local/django/mysite/apache/django.wsgi
By default, this will have Django instance running in embedded mode so
we want to override that. This would normally be done by configuring a
daemon process group and delegating application to run in it.
WSGIDaemonProcess django1 display-name=%{GROUP}
WSGIProcessGroup django1
The 'display-name' option here means that 'ps' command will show
'(wsgi:django1)' in output rather that 'httpd' process name. This will
be important for later on.
The way WSGIProcessGroup is used here means it is a static mapping, so
although we might be able to create more daemon process groups, you
would have to change the Apache configuration and restart Apache to be
able to delegate application to run in different daemon process group.
Obviously this isn't what we want.
So, instead of a static mapping, we use ability of mod_wsgi for the
process group to which application is delegated to be specified
dynamically. There are actually a number of ways this can be done when
using mod_wsgi, but will use a method which uses mod_rewrite as a
helper.
You indicated that there would be a cap on the number of instances of
Django that need to be running at any one time. Thus, what we will do
is pre define that many daemon process groups.
WSGIDaemonProcess django1 display-name=%{GROUP}
WSGIDaemonProcess django2 display-name=%{GROUP}
WSGIDaemonProcess django3 display-name=%{GROUP}
...
WSGIDaemonProcess djangon display-name=%{GROUP}
We also want which daemon process group is used based on identity of
logged in user. We will use HTTP Basic authentication as
authentication as that is the easiest. As long as you run stuff
through HTTPS using HTTP Basic authentication wouldn't be an issue.
Before we get onto how to use user identity from HTTP Basic
authentication, lets look at the dynamic mapping issue. To do this
what we are going to define is:
WSGIProcessGroup %{ENV:PROCESS_GROUP}
What this says is that name of process group should instead be source
from request environment variable called 'PROCESS_GROUP'. To set that,
we are going to use a rewrite rule and source the value to set it to
from a mapping file in the file system. Note using mapping file here
as it allows the value to then be set outside of Apache configuration
with Apache automatically picking up the change. Also key to when we
move on to dealing with user identity. Adding that we then have:
RewriteEngine On
RewriteMap procmap txt:/usr/local/django/mysite/apache/procmap.txt
RewriteRule . - [E=PROCESS_GROUP:${procmap:django|undefined}]
WSGIProcessGroup %{ENV:PROCESS_GROUP}
The 'procmap.txt' file will contain:
django django1
This file is read by Apache and cached, but will be reread when it
changes.
With the file written as is, means that Django instance will be
delegated to daemon process group called 'django1'. If you wanted it
instead to run in daemon process group called 'django2', you would
simply edit the 'procmap.txt' file and change it to:
django django2
If for some reason the file didn't contain key 'django' used in
rewrite rule, would use value of 'undefined' for process group name.
Since no such daemon process group defined, then mod_wsgi would return
500 error to request indicating that no valid daemon process group.
This sort of setup where delegation is manual may be a way of handling
swapping between application versions when upgrading a site, but we
need to introduce the identity of the user.
I will not show how to setup HTTP Basic authentication as you just
need to follow Apache documentation for that. Important thing to know
is that in using HTTP Basic authentication, the identity of the user
is then available to rewrite rules as "REMOTE_USER'. To use that, we
then change above to:
RewriteEngine On
RewriteMap procmap txt:/usr/local/django/mysite/apache/procmap.txt
RewriteRule . - [E=PROCESS_GROUP:${procmap:%{REMOTE_USER}|
undefined}]
WSGIProcessGroup %{ENV:PROCESS_GROUP}
The difference here is that instead of lookup key in rewrite map being
fixed value of 'django' we use the identity of the logged in user. The
'procmap.txt' file would then contain multiple entries, one per user
who could access the site.
graham django1
brian django2
macolm django3
Looking at that altogether, what we have is a pool of daemon process
groups that can be used and a way of dynamically, via the mapping
file, mapping different users requests into Django instances running
in those different daemon process group.
That is the multiplexing done, but it doesn't address how we have the
instance in each daemon process group use a different database.
Normally when using Django with Apache/mod_wsgi, the WSGI script file
would contain:
import os, sys
sys.path.append('/usr/local/django')
sys.path.append('/usr/local/django/mysite')
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
We don't want this though, as that would result in same Django
settings module and thus same database configuration being used for
each.
So, what we are going to do is to split this into two parts. The WSGI
script file will now just be:
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
All this is doing is setting the Django WSGI application entry point.
It is not setting up sys.path or defining what the Django settings
module is. For the latter, we will instead use a separate code file
and load a unique one for each process group. To do that we use the
WSGIImportScript directive to preload the configuration into the
daemon process group at startup.
One issue with WSGIImportScript though is that have to specify both
the process group and application group (sub interpreter) into which
the file should be loaded. Since at moment the application group will
depend on name of host, let us instead force Django to run in main
interpreter so can use known name. This is done using
WSGIApplicationGroup directive.
What we will then have is:
WSGIScriptAlias / /usr/local/django/mysite/apache/django.wsgi
WSGIApplicationGroup %{GLOBAL}
RewriteEngine On
RewriteMap procmap txt:/usr/local/django/mysite/apache/procmap.txt
RewriteRule . - [E=PROCESS_GROUP:${procmap:%{REMOTE_USER}|
undefined}]
WSGIProcessGroup %{ENV:PROCESS_GROUP}
WSGIDaemonProcess django1 display-name=%{GROUP}
WSGIImportScript /usr/local/django/mysite/apache/django1.wsgi \
process-group=django1 application-group=%{GLOBAL}
WSGIDaemonProcess django2 display-name=%{GROUP}
WSGIImportScript /usr/local/django/mysite/apache/django2.wsgi \
process-group=django2 application-group=%{GLOBAL}
WSGIDaemonProcess django3 display-name=%{GROUP}
WSGIImportScript /usr/local/django/mysite/apache/django-config3.wsgi
\
process-group=django3 application-group=%{GLOBAL}
...
WSGIDaemonProcess djangon display-name=%{GROUP}
WSGIImportScript /usr/local/django/mysite/apache/djangon.wsgi \
process-group=djangon application-group=%{GLOBAL}
Presuming we only want to override database setting, 'django1.wsgi'
would then have:
import os, sys
sys.path.append('/usr/local/django')
sys.path.append('/usr/local/django/mysite')
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
import mysite.settings
# Override per instance settings.
mysite.settings.DATABASE_HOST = ...
mysite.settings.DATABASE_NAME = ...
...
The file for 'django2.wsgi' would be similar but different overrides
for values.
Instead of importing Django settings module and overriding them, you
could also have distinct settings modules and just set
DJANGO_SETTINGS_MODULE differently for each.
What will now happen is that when Apache/mod_wsgi starts up a daemon
process, it will import that configuration script which will set
DJANGO_SETTINGS_MODULE and specify any overrides.
When a request actually arrives, then the normal WSGI script file is
loaded, along with actual Django code, but where configuration is
based on the preloaded script.
In this arrangement, the Django code will actually be lazily loaded,
that is, only on first request. If you wanted to preload that as well,
would just need to import necessary Django modules at end of config
script.
That is basically it and hopefully it makes sense. It may seem a bit
complex, but what you want to do isn't normal. Also, it all could have
been done a bit simpler if were using mod_wsgi 3.0 development code
from subversion trunk as with that have a way of avoiding use of
WSGIImportScript. This is because in that next version, name of daemon
process group is actually available from within WSGI script file at
global scope. Thus could have setting DJANGO_SETTINGS_MODULE in WSGI
script file such that it used name of process group. For example:
import mod_wsgi
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.%ssettings' %
mod_wsgi.process_group
Then all we need to do is have separate Django settings modules for
the instance in each process group.
The only thing to know cover is what to do when introducing and/or
removing users.
For that the steps would be:
1. Modify appropriate 'djangon.wsgi' config file referenced by
WSGIImportScript.
2. Use 'ps' to identity PID that that daemon process and send it
SIGINT. This will cause it to shutdown and restart.
3. Modify 'procpmap.txt' file to add in entry mapping new user to
appropriate 'djangon' process group.
4. Add new user into authentication user database.
For deletion, opposite done.
Anyway, work through that and see if it makes any sense at all. As I
said, could be a bit cleaner if using mod_wsgi 3.0 development
version, or if you want to make a 4 line code change to mod_wsgi 2.X
version you use. Could well be worth making the change just to make it
that little bit simpler.
BTW, also note this is all just typed in out of my head, I haven't
actually gone and tested it, although believe it should work. The only
issue you might run across is that the WSGIImportScript directive at
the moment actually has to go outside of any VirtualHost containers
where as WSGIDaemonProcess it applies to can be inside. There is a
ticket for mod_wsgi to address this, but haven't got around to fixing
it.
If there are any questions, let me know.
Graham