Merge "Handle DB creation race condition"

This commit is contained in:
Jenkins 2013-05-30 20:27:22 +00:00 committed by Gerrit Code Review
commit 5501a4031f
4 changed files with 46 additions and 12 deletions

View File

@ -113,8 +113,11 @@ class AccountController(object):
broker.pending_timeout = 3 broker.pending_timeout = 3
if account.startswith(self.auto_create_account_prefix) and \ if account.startswith(self.auto_create_account_prefix) and \
not os.path.exists(broker.db_file): not os.path.exists(broker.db_file):
try:
broker.initialize(normalize_timestamp( broker.initialize(normalize_timestamp(
req.headers.get('x-timestamp') or time.time())) req.headers.get('x-timestamp') or time.time()))
except swift.common.db.DatabaseAlreadyExists:
pass
if req.headers.get('x-account-override-deleted', 'no').lower() != \ if req.headers.get('x-account-override-deleted', 'no').lower() != \
'yes' and broker.is_deleted(): 'yes' and broker.is_deleted():
return HTTPNotFound(request=req) return HTTPNotFound(request=req)
@ -130,8 +133,11 @@ class AccountController(object):
else: # put account else: # put account
timestamp = normalize_timestamp(req.headers['x-timestamp']) timestamp = normalize_timestamp(req.headers['x-timestamp'])
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
try:
broker.initialize(timestamp) broker.initialize(timestamp)
created = True created = True
except swift.common.db.DatabaseAlreadyExists:
pass
elif broker.is_status_deleted(): elif broker.is_status_deleted():
return HTTPForbidden(request=req, body='Recently deleted') return HTTPForbidden(request=req, body='Recently deleted')
else: else:

View File

@ -70,6 +70,16 @@ class DatabaseConnectionError(sqlite3.DatabaseError):
self.path, self.timeout, self.msg) self.path, self.timeout, self.msg)
class DatabaseAlreadyExists(sqlite3.DatabaseError):
"""More friendly error messages for DB Errors."""
def __init__(self, path):
self.path = path
def __str__(self):
return 'DB %s already exists' % self.path
class GreenDBConnection(sqlite3.Connection): class GreenDBConnection(sqlite3.Connection):
"""SQLite DB Connection handler that plays well with eventlet.""" """SQLite DB Connection handler that plays well with eventlet."""
@ -247,9 +257,7 @@ class DatabaseBroker(object):
if os.path.exists(self.db_file): if os.path.exists(self.db_file):
# It's as if there was a "condition" where different parts # It's as if there was a "condition" where different parts
# of the system were "racing" each other. # of the system were "racing" each other.
raise DatabaseConnectionError( raise DatabaseAlreadyExists(self.db_file)
self.db_file,
'DB created by someone else while working?')
renamer(tmp_db_file, self.db_file) renamer(tmp_db_file, self.db_file)
self.conn = get_db_connection(self.db_file, self.timeout) self.conn = get_db_connection(self.db_file, self.timeout)
else: else:

View File

@ -198,8 +198,11 @@ class ContainerController(object):
broker = self._get_container_broker(drive, part, account, container) broker = self._get_container_broker(drive, part, account, container)
if account.startswith(self.auto_create_account_prefix) and obj and \ if account.startswith(self.auto_create_account_prefix) and obj and \
not os.path.exists(broker.db_file): not os.path.exists(broker.db_file):
try:
broker.initialize(normalize_timestamp( broker.initialize(normalize_timestamp(
req.headers.get('x-timestamp') or time.time())) req.headers.get('x-timestamp') or time.time()))
except swift.common.db.DatabaseAlreadyExists:
pass
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
return HTTPNotFound() return HTTPNotFound()
if obj: # delete object if obj: # delete object
@ -247,7 +250,10 @@ class ContainerController(object):
if obj: # put container object if obj: # put container object
if account.startswith(self.auto_create_account_prefix) and \ if account.startswith(self.auto_create_account_prefix) and \
not os.path.exists(broker.db_file): not os.path.exists(broker.db_file):
try:
broker.initialize(timestamp) broker.initialize(timestamp)
except swift.common.db.DatabaseAlreadyExists:
pass
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
return HTTPNotFound() return HTTPNotFound()
broker.put_object(obj, timestamp, int(req.headers['x-size']), broker.put_object(obj, timestamp, int(req.headers['x-size']),
@ -256,8 +262,11 @@ class ContainerController(object):
return HTTPCreated(request=req) return HTTPCreated(request=req)
else: # put container else: # put container
if not os.path.exists(broker.db_file): if not os.path.exists(broker.db_file):
try:
broker.initialize(timestamp) broker.initialize(timestamp)
created = True created = True
except swift.common.db.DatabaseAlreadyExists:
pass
else: else:
created = broker.is_deleted() created = broker.is_deleted()
broker.update_put_timestamp(timestamp) broker.update_put_timestamp(timestamp)

View File

@ -25,6 +25,7 @@ from uuid import uuid4
import simplejson import simplejson
import sqlite3 import sqlite3
from mock import patch
import swift.common.db import swift.common.db
from swift.common.db import AccountBroker, chexor, ContainerBroker, \ from swift.common.db import AccountBroker, chexor, ContainerBroker, \
@ -99,6 +100,7 @@ class TestDatabaseBroker(unittest.TestCase):
def test_DB_PREALLOCATION_setting(self): def test_DB_PREALLOCATION_setting(self):
u = uuid4().hex u = uuid4().hex
b = DatabaseBroker(u) b = DatabaseBroker(u)
swift.common.db.DB_PREALLOCATION = False
b._preallocate() b._preallocate()
swift.common.db.DB_PREALLOCATION = True swift.common.db.DB_PREALLOCATION = True
self.assertRaises(OSError, b._preallocate) self.assertRaises(OSError, b._preallocate)
@ -147,6 +149,15 @@ class TestDatabaseBroker(unittest.TestCase):
conn.execute('SELECT * FROM outgoing_sync') conn.execute('SELECT * FROM outgoing_sync')
conn.execute('SELECT * FROM incoming_sync') conn.execute('SELECT * FROM incoming_sync')
def my_exists(*a, **kw):
return True
with patch('os.path.exists', my_exists):
broker = DatabaseBroker(os.path.join(self.testdir, '1.db'))
broker._initialize = stub
self.assertRaises(swift.common.db.DatabaseAlreadyExists,
broker.initialize, normalize_timestamp('1'))
def test_delete_db(self): def test_delete_db(self):
def init_stub(conn, put_timestamp): def init_stub(conn, put_timestamp):
conn.execute('CREATE TABLE test (one TEXT)') conn.execute('CREATE TABLE test (one TEXT)')