1
# Copyright (c) 2013 Rackspace Hosting
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
8
# http://www.apache.org/licenses/LICENSE-2.0
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
16
"""Multiple DB API backend support.
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
28
from heat.openstack.common.db import exception
29
from heat.openstack.common.gettextutils import _LE
30
from heat.openstack.common import importutils
33
LOG = logging.getLogger(__name__)
36
def safe_for_db_retry(f):
37
"""Enable db-retry for decorated function, if config option enabled."""
38
f.__dict__['enable_retry'] = True
42
class wrap_db_retry(object):
43
"""Retry db.api methods, if DBConnectionError() raised
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.
52
def __init__(self, retry_interval, max_retries, inc_retry_interval,
54
super(wrap_db_retry, self).__init__()
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
61
def __call__(self, f):
63
def wrapper(*args, **kwargs):
64
next_interval = self.retry_interval
65
remaining = self.max_retries
69
return f(*args, **kwargs)
70
except exception.DBConnectionError as e:
72
LOG.exception(_LE('DB exceeded retry limit.'))
73
raise exception.DBError(e)
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:
84
self.max_retry_interval
90
def __init__(self, backend_name, backend_mapping=None, lazy=False,
92
"""Initialize the chosen DB API backend.
94
:param backend_name: name of the backend to load
95
:type backend_name: str
97
:param backend_mapping: backend name -> module/class to load mapping
98
:type backend_mapping: dict
100
:param lazy: load the DB backend lazily on the first DB API method call
105
:keyword use_db_reconnect: retry DB transactions on disconnect or not
106
:type use_db_reconnect: bool
108
:keyword retry_interval: seconds between transaction retries
109
:type retry_interval: int
111
:keyword inc_retry_interval: increase retry interval or not
112
:type inc_retry_interval: bool
114
:keyword max_retry_interval: max interval value between retries
115
:type max_retry_interval: int
117
:keyword max_retries: max number of retries before an error is raised
118
:type max_retries: int
123
self._backend_name = backend_name
124
self._backend_mapping = backend_mapping or {}
125
self._lock = threading.Lock()
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)
136
def _load_backend(self):
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,
142
backend_mod = importutils.import_module(backend_path)
143
self._backend = backend_mod.get_backend()
145
def __getattr__(self, key):
146
if not self._backend:
149
attr = getattr(self._backend, key)
150
if not hasattr(attr, '__call__'):
152
# NOTE(vsergeyev): If `use_db_reconnect` option is set to True, retry
153
# DB API methods, decorated with @safe_for_db_retry
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)