~ubuntu-branches/ubuntu/utopic/heat/utopic-updates

« back to all changes in this revision

Viewing changes to heat/openstack/common/db/api.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2014-09-08 09:40:59 UTC
  • mfrom: (1.1.16)
  • Revision ID: package-import@ubuntu.com-20140908094059-pzysrm0uy4senjez
Tags: 2014.2~b3-0ubuntu1
New upstream version. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2013 Rackspace Hosting
2
 
# All Rights Reserved.
3
 
#
4
 
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
 
#    not use this file except in compliance with the License. You may obtain
6
 
#    a copy of the License at
7
 
#
8
 
#         http://www.apache.org/licenses/LICENSE-2.0
9
 
#
10
 
#    Unless required by applicable law or agreed to in writing, software
11
 
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 
#    License for the specific language governing permissions and limitations
14
 
#    under the License.
15
 
 
16
 
"""Multiple DB API backend support.
17
 
 
18
 
A DB backend module should implement a method named 'get_backend' which
19
 
takes no arguments.  The method can return any object that implements DB
20
 
API methods.
21
 
"""
22
 
 
23
 
import functools
24
 
import logging
25
 
import threading
26
 
import time
27
 
 
28
 
from heat.openstack.common.db import exception
29
 
from heat.openstack.common.gettextutils import _LE
30
 
from heat.openstack.common import importutils
31
 
 
32
 
 
33
 
LOG = logging.getLogger(__name__)
34
 
 
35
 
 
36
 
def safe_for_db_retry(f):
37
 
    """Enable db-retry for decorated function, if config option enabled."""
38
 
    f.__dict__['enable_retry'] = True
39
 
    return f
40
 
 
41
 
 
42
 
class wrap_db_retry(object):
43
 
    """Retry db.api methods, if DBConnectionError() raised
44
 
 
45
 
    Retry decorated db.api methods. If we enabled `use_db_reconnect`
46
 
    in config, this decorator will be applied to all db.api functions,
47
 
    marked with @safe_for_db_retry decorator.
48
 
    Decorator catchs DBConnectionError() and retries function in a
49
 
    loop until it succeeds, or until maximum retries count will be reached.
50
 
    """
51
 
 
52
 
    def __init__(self, retry_interval, max_retries, inc_retry_interval,
53
 
                 max_retry_interval):
54
 
        super(wrap_db_retry, self).__init__()
55
 
 
56
 
        self.retry_interval = retry_interval
57
 
        self.max_retries = max_retries
58
 
        self.inc_retry_interval = inc_retry_interval
59
 
        self.max_retry_interval = max_retry_interval
60
 
 
61
 
    def __call__(self, f):
62
 
        @functools.wraps(f)
63
 
        def wrapper(*args, **kwargs):
64
 
            next_interval = self.retry_interval
65
 
            remaining = self.max_retries
66
 
 
67
 
            while True:
68
 
                try:
69
 
                    return f(*args, **kwargs)
70
 
                except exception.DBConnectionError as e:
71
 
                    if remaining == 0:
72
 
                        LOG.exception(_LE('DB exceeded retry limit.'))
73
 
                        raise exception.DBError(e)
74
 
                    if remaining != -1:
75
 
                        remaining -= 1
76
 
                        LOG.exception(_LE('DB connection error.'))
77
 
                    # NOTE(vsergeyev): We are using patched time module, so
78
 
                    #                  this effectively yields the execution
79
 
                    #                  context to another green thread.
80
 
                    time.sleep(next_interval)
81
 
                    if self.inc_retry_interval:
82
 
                        next_interval = min(
83
 
                            next_interval * 2,
84
 
                            self.max_retry_interval
85
 
                        )
86
 
        return wrapper
87
 
 
88
 
 
89
 
class DBAPI(object):
90
 
    def __init__(self, backend_name, backend_mapping=None, lazy=False,
91
 
                 **kwargs):
92
 
        """Initialize the chosen DB API backend.
93
 
 
94
 
        :param backend_name: name of the backend to load
95
 
        :type backend_name: str
96
 
 
97
 
        :param backend_mapping: backend name -> module/class to load mapping
98
 
        :type backend_mapping: dict
99
 
 
100
 
        :param lazy: load the DB backend lazily on the first DB API method call
101
 
        :type lazy: bool
102
 
 
103
 
        Keyword arguments:
104
 
 
105
 
        :keyword use_db_reconnect: retry DB transactions on disconnect or not
106
 
        :type use_db_reconnect: bool
107
 
 
108
 
        :keyword retry_interval: seconds between transaction retries
109
 
        :type retry_interval: int
110
 
 
111
 
        :keyword inc_retry_interval: increase retry interval or not
112
 
        :type inc_retry_interval: bool
113
 
 
114
 
        :keyword max_retry_interval: max interval value between retries
115
 
        :type max_retry_interval: int
116
 
 
117
 
        :keyword max_retries: max number of retries before an error is raised
118
 
        :type max_retries: int
119
 
 
120
 
        """
121
 
 
122
 
        self._backend = None
123
 
        self._backend_name = backend_name
124
 
        self._backend_mapping = backend_mapping or {}
125
 
        self._lock = threading.Lock()
126
 
 
127
 
        if not lazy:
128
 
            self._load_backend()
129
 
 
130
 
        self.use_db_reconnect = kwargs.get('use_db_reconnect', False)
131
 
        self.retry_interval = kwargs.get('retry_interval', 1)
132
 
        self.inc_retry_interval = kwargs.get('inc_retry_interval', True)
133
 
        self.max_retry_interval = kwargs.get('max_retry_interval', 10)
134
 
        self.max_retries = kwargs.get('max_retries', 20)
135
 
 
136
 
    def _load_backend(self):
137
 
        with self._lock:
138
 
            if not self._backend:
139
 
                # Import the untranslated name if we don't have a mapping
140
 
                backend_path = self._backend_mapping.get(self._backend_name,
141
 
                                                         self._backend_name)
142
 
                backend_mod = importutils.import_module(backend_path)
143
 
                self._backend = backend_mod.get_backend()
144
 
 
145
 
    def __getattr__(self, key):
146
 
        if not self._backend:
147
 
            self._load_backend()
148
 
 
149
 
        attr = getattr(self._backend, key)
150
 
        if not hasattr(attr, '__call__'):
151
 
            return attr
152
 
        # NOTE(vsergeyev): If `use_db_reconnect` option is set to True, retry
153
 
        #                  DB API methods, decorated with @safe_for_db_retry
154
 
        #                  on disconnect.
155
 
        if self.use_db_reconnect and hasattr(attr, 'enable_retry'):
156
 
            attr = wrap_db_retry(
157
 
                retry_interval=self.retry_interval,
158
 
                max_retries=self.max_retries,
159
 
                inc_retry_interval=self.inc_retry_interval,
160
 
                max_retry_interval=self.max_retry_interval)(attr)
161
 
 
162
 
        return attr