1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00
Files
quay/data/test/test_readreplica.py
Syed Ahmed 97b3e025de db: use read replica for selected queries (PROJQUAY-6397) (#2758)
* db: use read replica for selected queries (PROJQUAY-6397)

We add a new param `can_use_read_replica` to the `select`
query. This allows us to choose which queries we want to
send to the read replica. This is useful in cases where
the read replica lags behind the primary and some queries
need the latest data
2024-03-18 14:23:16 -04:00

121 lines
4.1 KiB
Python

import os
import shutil
import pytest
from peewee import OperationalError
from data.database import User, configure, db_disallow_replica_use, read_only_config
from data.readreplica import ReadOnlyModeException
from test.fixtures import *
from test.testconfig import FakeTransaction
@pytest.mark.skipif(bool(os.environ.get("TEST_DATABASE_URI")), reason="Testing requires SQLite")
def test_readreplica(init_db_path, tmpdir_factory):
primary_file = str(tmpdir_factory.mktemp("data").join("primary.db"))
replica_file = str(tmpdir_factory.mktemp("data").join("replica.db"))
# Copy the initialized database to two different locations.
shutil.copy2(init_db_path, primary_file)
shutil.copy2(init_db_path, replica_file)
db_config = {
"DB_URI": "sqlite:///{0}".format(primary_file),
"DB_READ_REPLICAS": [
{"DB_URI": "sqlite:///{0}".format(replica_file)},
],
"DB_CONNECTION_ARGS": {
"threadlocals": True,
"autorollback": True,
},
"DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
"FOR_TESTING": True,
"DATABASE_SECRET_KEY": "anothercrazykey!",
}
# Initialize the DB with the primary and the replica.
configure(db_config)
assert not read_only_config.obj.is_readonly
assert read_only_config.obj.read_replicas
# Ensure we can read the data.
devtable_user = User.get(username="devtable")
assert devtable_user.username == "devtable"
# Configure with a bad primary. Reading should still work since we're hitting the replica.
db_config["DB_URI"] = "sqlite:///does/not/exist"
configure(db_config)
assert not read_only_config.obj.is_readonly
assert read_only_config.obj.read_replicas
devtable_user = User.get(username="devtable", can_use_read_replica=True)
assert devtable_user.username == "devtable"
# Force us to hit the master and ensure it doesn't work.
with db_disallow_replica_use():
with pytest.raises(OperationalError):
User.get(username="devtable")
# Explicitly disallow replica use and ensure it doesn't work.
with pytest.raises(OperationalError):
User.get(username="devtable", can_use_read_replica=False)
# Default to hitting the master and ensure it doesn't work.
with pytest.raises(OperationalError):
User.get(username="devtable")
# Test read replica again.
devtable_user = User.get(username="devtable", can_use_read_replica=True)
assert devtable_user.username == "devtable"
# Try to change some data. This should fail because the primary is broken.
with pytest.raises(OperationalError):
devtable_user.email = "newlychanged"
devtable_user.save()
# Fix the primary and try again.
db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
configure(db_config)
assert not read_only_config.obj.is_readonly
assert read_only_config.obj.read_replicas
devtable_user.email = "newlychanged"
devtable_user.save()
# Mark the system as readonly.
db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
db_config["REGISTRY_STATE"] = "readonly"
configure(db_config)
assert read_only_config.obj.is_readonly
assert read_only_config.obj.read_replicas
# Ensure all write operations raise a readonly mode exception.
with pytest.raises(ReadOnlyModeException):
devtable_user.email = "newlychanged2"
devtable_user.save()
with pytest.raises(ReadOnlyModeException):
User.create(username="foo")
with pytest.raises(ReadOnlyModeException):
User.delete().where(User.username == "foo").execute()
with pytest.raises(ReadOnlyModeException):
User.update(username="bar").where(User.username == "foo").execute()
# Reset the config on the DB, so we don't mess up other tests.
configure(
{
"DB_URI": "sqlite:///{0}".format(primary_file),
"DB_CONNECTION_ARGS": {
"threadlocals": True,
"autorollback": True,
},
"DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
"DATABASE_SECRET_KEY": "anothercrazykey!",
}
)