Seamlessly switch off (and on) a Django (or other WSGI) site for upgrades

May 25th, 2009 by tobias

In preparation for migrating the EveryWatt database from one machine to another, I wrote this little WSGI script to easily disable the site while I copy the data. Since it doesn’t depend on Django or really anything else (other than a functioning WSGI server), you can use it for other upgrades, too.

This is useful for preventing updates to the database while you, for example, dump the database on one machine and load it on another. With everything else already in place on either side, the user should only see the “Upgrade in progress” message for a few minutes.

Since EveryWatt includes a number of data logger clients that upload utility meter readings to the site through its Open API, I wanted to make sure any POST attempts received a temporary failure message (the data logger will store the data and retry the POST every minute)–hence the 405 Method Not Allowed for all non-GET requests.

Here’s the script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import os
import sys
 
UPGRADING = False
 
#Calculate the project path based on the location of the WSGI script.
project_dir = os.path.dirname(__file__)
sys.path.append(project_dir)
 
def upgrade_in_progress(environ, start_response):
    upgrade_file = os.path.join(project_dir, 'media', 'html', 'upgrade.html')
    if os.path.exists(upgrade_file):
        response_headers = [('Content-type','text/html')]
        response = open(upgrade_file).read()
    else:
        response_headers = [('Content-type','text/plain')]
        response = 'Application upgrade in progress...please check back soon.'
 
    if environ['REQUEST_METHOD'] == 'GET':
        status = '503 Service Unavailable'
    else:
        status = '405 Method Not Allowed'
    start_response(status, response_headers)
    return [response]
 
if UPGRADING:
    application = upgrade_in_progress
else:
    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
    import django.core.handlers.wsgi
    application = django.core.handlers.wsgi.WSGIHandler()

And in case you need it, here’s one way to dump a PostgreSQL database on one machine while you load it on another, to be run on the new host, as the database superuser:

1
pg_dump -h <old host> -U <user> <old database> | psql <new database>

Good luck and please post your questions/comments.

4 Responses to “Seamlessly switch off (and on) a Django (or other WSGI) site for upgrades”

  1. Carl Meyer Says:

    Nice. It should return a 503 Service Unavailable status code rather than 200 OK, else you run the risk of a crawler indexing your “upgrading” page. Using the 503 status code won’t affect browsers displaying the content of the temp page.

    If you really want to be HTTP-friendly, you’d optionally add a Retry-After header to indicate how long the maintenance is expected to take.

  2. tobias Says:

    Good call, thanks. I updated the code to return a 503 instead of a 200.

  3. masklinn Says:

    I’d also suggest returning 405 Method Not Allowed rather than 403 Forbidden if the URL is called with something other than GET.

  4. tobias Says:

    That sounds reasonable, thanks. 405 Method Not Allowed sounded more permanent to me originally (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html), since POST is normally a valid method. From what I understand, most of the 4xx error codes can be either temporary or permanent, as long as the body indicates which it is. Post updated.

Leave a Reply