~ubuntuone-pqm-team/canonical-identity-provider/trunk

« back to all changes in this revision

Viewing changes to identityprovider/middleware/dbfailover.py

  • Committer: Danny Tamez
  • Date: 2010-04-21 15:29:24 UTC
  • Revision ID: danny.tamez@canonical.com-20100421152924-lq1m92tstk2iz75a
Canonical SSO Provider (Open Source) - Initial Commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
from django.conf import settings
 
5
from django.db import connection
 
6
from django.utils.translation import ugettext as _
 
7
 
 
8
from identityprovider.readonly import readonly_manager
 
9
from identityprovider.views.errors import ErrorPage
 
10
 
 
11
 
 
12
class NoBackupError(Exception):
 
13
    pass
 
14
 
 
15
 
 
16
class DBFailoverMiddleware(object):
 
17
    """Database failover middleware.
 
18
 
 
19
    This middleware will attempt to establish a minimal connection to the
 
20
    configured database before starting each request.  If it fails for
 
21
    some reason, it will enter readonly mode and switch to some other
 
22
    configured backup database.
 
23
 
 
24
    Available database connections are configured with the DB_CONNECTIONS
 
25
    setting, which is a list of dictionaries, each with DATABASE_ID,
 
26
    DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER and
 
27
    DATABASE_PASSWORD keys.
 
28
 
 
29
    When the main database connection fails, the backup connections
 
30
    are tried in order.
 
31
    """
 
32
 
 
33
    def process_request(self, request):
 
34
        try:
 
35
            connection_ok = False
 
36
            failed_attempts = 0
 
37
            max_attempts = getattr(settings, 'DBFAILOVER_ATTEMPTS', 1)
 
38
            readonly_manager.check_readonly()
 
39
            self.choose_connection()
 
40
            while not connection_ok:
 
41
                try:
 
42
                    cursor = connection.cursor()
 
43
                    cursor.execute('SELECT 42')
 
44
                    connection_ok = True
 
45
                except Exception, e:
 
46
                    # I'm just catching all exceptions here because the
 
47
                    # DB-API doesn't define a standard set of exceptions
 
48
                    # to catch, so we'd need to start catching
 
49
                    # engine-specific stuff.
 
50
                    failed_attempts += 1
 
51
                    if failed_attempts == max_attempts:
 
52
                        readonly_manager.mark_current_failed()
 
53
                        self.choose_connection()
 
54
                        failed_attempts = 0
 
55
        except NoBackupError:
 
56
            return ErrorPage(500)(request)
 
57
 
 
58
    def process_response(self, request, response):
 
59
        """ Add a http header to indicate if we're currently in RO-mode """
 
60
        if settings.DEBUG:
 
61
            response['X-Read-Only'] = str(readonly_manager.is_readonly())
 
62
        return response
 
63
 
 
64
    def choose_connection(self):
 
65
        """ Configure the first database connection that isn't already marked
 
66
        as failed, if any.
 
67
        """
 
68
        for db in readonly_manager.connections:
 
69
            if not readonly_manager.is_failed(db['DATABASE_ID']):
 
70
                readonly_manager.set_db(db)
 
71
                break
 
72
        else:
 
73
            raise NoBackupError(_("No backup connections left"))