mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
* Remove V3_UPGRADE_MODE * Remove tag backfill worker and all callers to tag backfill and upgrades * Change initdb to create all test data via the manifest builder, rather than manually via legacy images * Convert various code paths to use the registry_model where we previously did not do so * Convert the GC test suite to build via the manifest builder * Delete an old, unused tool * Delete the Pre OCI registry model * Add additional error handling to the manifest creation code path * Add additional error handling to the OCI tag creation code path * Change how we respond to invalid manifest content types to better handle unknowns * Change legacy secscan test suite to use the registry model * Change the repo build badge to use the registry model (also fixes a bug) * Delete now-unused data model code * Remove old model adjustment code from OCI model * Mark older data models as deprecated which will prevent new rows from being inserted * Remove references to old registry test suite from various testing files * Remove tag backfill worker (again; got re-added during rebase) * Move all deprecated model checks into a central function * Make data_migration more Pythonic * Small requested fixes to Tag module styling * Have tag backfill worker fail to migrate if there are TagManifest's Since this backfill should only be called in future releases for empty models, this should catch someone attempting to upgrade from a too-old version * Remove labelbackfillworker as it is no longer needed * Remove unused invalid import * Reimplement the tag test for the remaining method used
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
import random
|
|
|
|
from collections import namedtuple
|
|
|
|
from peewee import Model, SENTINEL, OperationalError, Proxy
|
|
|
|
from data.decorators import is_deprecated_model
|
|
|
|
|
|
ReadOnlyConfig = namedtuple("ReadOnlyConfig", ["is_readonly", "read_replicas"])
|
|
|
|
|
|
class ReadOnlyModeException(Exception):
|
|
"""
|
|
Exception raised if a write operation was attempted when in read only mode.
|
|
"""
|
|
|
|
|
|
class AutomaticFailoverWrapper(object):
|
|
"""
|
|
Class which wraps a peewee database driver and (optionally) a second driver.
|
|
|
|
When executing SQL, if an OperationalError occurs, if a second driver is given, the query is
|
|
attempted again on the fallback DB. Otherwise, the exception is raised.
|
|
"""
|
|
|
|
def __init__(self, primary_db, fallback_db=None):
|
|
self._primary_db = primary_db
|
|
self._fallback_db = fallback_db
|
|
|
|
def __getattr__(self, attribute):
|
|
if attribute != "execute_sql" and hasattr(self._primary_db, attribute):
|
|
return getattr(self._primary_db, attribute)
|
|
|
|
return getattr(self, attribute)
|
|
|
|
def execute(self, query, commit=SENTINEL, **context_options):
|
|
ctx = self.get_sql_context(**context_options)
|
|
sql, params = ctx.sql(query).query()
|
|
return self.execute_sql(sql, params, commit=commit)
|
|
|
|
def execute_sql(self, sql, params=None, commit=SENTINEL):
|
|
try:
|
|
return self._primary_db.execute_sql(sql, params, commit)
|
|
except OperationalError:
|
|
if self._fallback_db is not None:
|
|
try:
|
|
return self._fallback_db.execute_sql(sql, params, commit)
|
|
except OperationalError:
|
|
raise
|
|
|
|
|
|
class ReadReplicaSupportedModel(Model):
|
|
"""
|
|
Base model for peewee data models that support using a read replica for SELECT requests not
|
|
under transactions, and automatic failover to the master if the read replica fails.
|
|
|
|
Read-only queries are initially attempted on one of the read replica databases
|
|
being used; if an OperationalError occurs when attempting to invoke the query,
|
|
then the failure is logged and the query is retried on the database master.
|
|
|
|
Queries that are non-SELECTs (or under transactions) are always tried on the
|
|
master.
|
|
|
|
If the system is configured into read only mode, then all non-read-only queries
|
|
will raise a ReadOnlyModeException.
|
|
"""
|
|
|
|
@classmethod
|
|
def _read_only_config(cls):
|
|
read_only_config = getattr(cls._meta, "read_only_config", None)
|
|
if read_only_config is None:
|
|
return ReadOnlyConfig(False, [])
|
|
|
|
if isinstance(read_only_config, Proxy) and read_only_config.obj is None:
|
|
return ReadOnlyConfig(False, [])
|
|
|
|
return read_only_config.obj or ReadOnlyConfig(False, [])
|
|
|
|
@classmethod
|
|
def _in_readonly_mode(cls):
|
|
return cls._read_only_config().is_readonly
|
|
|
|
@classmethod
|
|
def _select_database(cls):
|
|
"""
|
|
Selects a read replica database if we're configured to support read replicas.
|
|
|
|
Otherwise, selects the master database.
|
|
"""
|
|
# Select the master DB if read replica support is not enabled.
|
|
read_only_config = cls._read_only_config()
|
|
if not read_only_config.read_replicas:
|
|
return cls._meta.database
|
|
|
|
# Select the master DB if we're ever under a transaction.
|
|
if cls._meta.database.transaction_depth() > 0:
|
|
return cls._meta.database
|
|
|
|
# Otherwise, return a read replica database with auto-retry onto the main database.
|
|
replicas = read_only_config.read_replicas
|
|
selected_read_replica = replicas[random.randrange(len(replicas))]
|
|
return AutomaticFailoverWrapper(selected_read_replica, cls._meta.database)
|
|
|
|
@classmethod
|
|
def select(cls, *args, **kwargs):
|
|
query = super(ReadReplicaSupportedModel, cls).select(*args, **kwargs)
|
|
query._database = cls._select_database()
|
|
return query
|
|
|
|
@classmethod
|
|
def insert(cls, *args, **kwargs):
|
|
if is_deprecated_model(cls):
|
|
raise Exception("Attempt to write to deprecated model %s" % cls)
|
|
|
|
query = super(ReadReplicaSupportedModel, cls).insert(*args, **kwargs)
|
|
if cls._in_readonly_mode():
|
|
raise ReadOnlyModeException()
|
|
return query
|
|
|
|
@classmethod
|
|
def update(cls, *args, **kwargs):
|
|
query = super(ReadReplicaSupportedModel, cls).update(*args, **kwargs)
|
|
if cls._in_readonly_mode():
|
|
raise ReadOnlyModeException()
|
|
return query
|
|
|
|
@classmethod
|
|
def delete(cls, *args, **kwargs):
|
|
query = super(ReadReplicaSupportedModel, cls).delete(*args, **kwargs)
|
|
if cls._in_readonly_mode():
|
|
raise ReadOnlyModeException()
|
|
return query
|
|
|
|
@classmethod
|
|
def raw(cls, *args, **kwargs):
|
|
query = super(ReadReplicaSupportedModel, cls).raw(*args, **kwargs)
|
|
if query._sql.lower().startswith("select "):
|
|
query._database = cls._select_database()
|
|
elif cls._in_readonly_mode():
|
|
raise ReadOnlyModeException()
|
|
elif query._sql.lower().startswith("insert "):
|
|
if is_deprecated_model(cls):
|
|
raise Exception("Attempt to write to deprecated model %s" % cls)
|
|
|
|
return query
|