chore: Update database changes application logic in utils and add more RW tests in scheduler + Fix potential bugs and infinite loops

This commit is contained in:
Théophile Diot 2024-06-07 12:02:03 +01:00
parent b5a5bd2dd1
commit 186496fe4a
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
4 changed files with 119 additions and 30 deletions

View file

@ -482,7 +482,7 @@ class Database:
return data
def check_changes(self) -> Union[Dict[str, bool], bool, str]:
def check_changes(self, with_date: bool = False) -> Union[Dict[str, Any], str]:
"""Check if either the config, the custom configs, plugins or instances have changed inside the database"""
with self.__db_session() as session:
try:
@ -490,30 +490,66 @@ class Database:
session.query(Metadata)
.with_entities(
Metadata.custom_configs_changed,
Metadata.last_custom_configs_change,
Metadata.external_plugins_changed,
Metadata.last_external_plugins_change,
Metadata.pro_plugins_changed,
Metadata.last_pro_plugins_change,
Metadata.instances_changed,
Metadata.last_instances_change,
)
.filter_by(id=1)
.first()
)
return dict(
custom_configs_changed=metadata is not None and metadata.custom_configs_changed,
external_plugins_changed=metadata is not None and metadata.external_plugins_changed,
pro_plugins_changed=metadata is not None and metadata.pro_plugins_changed,
instances_changed=metadata is not None and metadata.instances_changed,
plugins_config_changed=[plugin.id for plugin in session.query(Plugins).with_entities(Plugins.id).filter_by(config_changed=True).all()],
)
except BaseException as e:
return str(e)
base_data = {
"custom_configs_changed": False,
"external_plugins_changed": False,
"pro_plugins_changed": False,
"instances_changed": False,
}
def check_plugin_changes(self) -> Union[List[str], str]:
"""Check if the plugins have changed inside the database"""
with self.__db_session() as session:
try:
plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(config_changed=True).all()
return [plugin.id for plugin in plugins]
if with_date:
data = base_data | {
"last_custom_configs_change": None,
"last_external_plugins_change": None,
"last_pro_plugins_change": None,
"last_instances_change": None,
"plugins_config_changed": {
plugin.id: plugin.last_config_change
for plugin in session.query(Plugins).with_entities(Plugins.id, Plugins.last_config_change).filter_by(config_changed=True).all()
},
}
if not metadata:
return data
return data | {
"custom_configs_changed": metadata.custom_configs_changed,
"last_custom_configs_change": metadata.last_custom_configs_change,
"external_plugins_changed": metadata.external_plugins_changed,
"last_external_plugins_change": metadata.last_external_plugins_change,
"pro_plugins_changed": metadata.pro_plugins_changed,
"last_pro_plugins_change": metadata.last_pro_plugins_change,
"instances_changed": metadata.instances_changed,
"last_instances_change": metadata.last_instances_change,
}
data = base_data | {
"plugins_config_changed": sorted(
plugin.id for plugin in session.query(Plugins).with_entities(Plugins.id).filter_by(config_changed=True).all()
),
}
if not metadata:
return data
return data | {
"custom_configs_changed": metadata.custom_configs_changed,
"external_plugins_changed": metadata.external_plugins_changed,
"pro_plugins_changed": metadata.pro_plugins_changed,
"instances_changed": metadata.instances_changed,
}
except BaseException as e:
return str(e)
@ -536,23 +572,31 @@ class Database:
if not metadata:
return "The metadata are not set yet, try again"
current_time = datetime.now()
if "config" in changes:
if not metadata.first_config_saved:
metadata.first_config_saved = True
if "custom_configs" in changes:
metadata.custom_configs_changed = value
metadata.last_custom_configs_change = current_time
if "external_plugins" in changes:
metadata.external_plugins_changed = value
metadata.last_external_plugins_change = current_time
if "pro_plugins" in changes:
metadata.pro_plugins_changed = value
metadata.last_pro_plugins_change = current_time
if "instances" in changes:
metadata.instances_changed = value
metadata.last_instances_change = current_time
if plugins_changes:
if plugins_changes == "all":
session.query(Plugins).update({Plugins.config_changed: value})
session.query(Plugins).update({Plugins.config_changed: value, Plugins.last_config_change: current_time})
else:
session.query(Plugins).filter(Plugins.id.in_(plugins_changes)).update({Plugins.config_changed: value})
session.query(Plugins).filter(Plugins.id.in_(plugins_changes)).update(
{Plugins.config_changed: value, Plugins.last_config_change: current_time}
)
session.commit()
except BaseException as e:
@ -581,7 +625,7 @@ class Database:
if db_version != bunkerweb_version:
self.logger.warning(f"Database version ({db_version}) is different from Bunkerweb version ({bunkerweb_version}), migrating ...")
curren_time = datetime.now()
current_time = datetime.now()
error = True
while error:
try:
@ -589,7 +633,7 @@ class Database:
metadata.reflect(self.sql_engine)
error = False
except BaseException as e:
if (datetime.now() - curren_time).total_seconds() > 10:
if (datetime.now() - current_time).total_seconds() > 10:
raise e
sleep(1)
@ -1455,6 +1499,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.custom_configs_changed = True
metadata.last_custom_configs_change = datetime.now()
try:
session.add_all(to_put)
@ -2181,8 +2226,10 @@ class Database:
if metadata is not None:
if _type == "external":
metadata.external_plugins_changed = True
metadata.last_external_plugins_change = datetime.now()
elif _type == "pro":
metadata.pro_plugins_changed = True
metadata.last_pro_plugins_change = datetime.now()
try:
session.add_all(to_put)
@ -2398,6 +2445,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.instances_changed = True
metadata.last_instances_change = datetime.now()
try:
session.commit()
@ -2429,6 +2477,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.instances_changed = True
metadata.last_instances_change = datetime.now()
try:
session.add_all(to_put)

View file

@ -59,6 +59,7 @@ class Plugins(Base):
data = Column(LargeBinary(length=(2**32) - 1), nullable=True)
checksum = Column(String(128), nullable=True)
config_changed = Column(Boolean, default=False, nullable=True)
last_config_change = Column(DateTime, nullable=True)
settings = relationship("Settings", back_populates="plugin", cascade="all, delete-orphan")
jobs = relationship("Jobs", back_populates="plugin", cascade="all, delete-orphan")
@ -242,8 +243,12 @@ class Metadata(Base):
autoconf_loaded = Column(Boolean, default=False, nullable=True)
scheduler_first_start = Column(Boolean, nullable=True)
custom_configs_changed = Column(Boolean, default=False, nullable=True)
last_custom_configs_change = Column(DateTime, nullable=True)
external_plugins_changed = Column(Boolean, default=False, nullable=True)
last_external_plugins_change = Column(DateTime, nullable=True)
pro_plugins_changed = Column(Boolean, default=False, nullable=True)
last_pro_plugins_change = Column(DateTime, nullable=True)
instances_changed = Column(Boolean, default=False, nullable=True)
last_instances_change = Column(DateTime, nullable=True)
integration = Column(INTEGRATIONS_ENUM, default="Unknown", nullable=False)
version = Column(String(32), default="1.5.8", nullable=False)

View file

@ -360,14 +360,16 @@ class JobScheduler(ApiCaller):
return False
return ret
def try_database_readonly(self) -> bool:
def try_database_readonly(self, force: bool = False) -> bool:
if not self.db.readonly:
try:
self.db.test_write()
self.db.readonly = False
return False
except BaseException:
self.db.readonly = True
return True
elif self.db.last_connection_retry and (datetime.now() - self.db.last_connection_retry).total_seconds() > 30:
elif not force and self.db.last_connection_retry and (datetime.now() - self.db.last_connection_retry).total_seconds() > 30:
return True
if self.db.database_uri and self.db.readonly:

View file

@ -663,6 +663,7 @@ if __name__ == "__main__":
Thread(target=listen_for_instances_reload, name="listen_for_instances_reload").start()
changed_plugins = []
old_changes = {}
while True:
threads.clear()
@ -788,7 +789,7 @@ if __name__ == "__main__":
while RUN and not NEED_RELOAD:
try:
SCHEDULER.run_pending()
sleep(1)
sleep(3 if SCHEDULER.db.readonly else 1)
current_time = datetime.now()
while DB_LOCK_FILE.is_file() and DB_LOCK_FILE.stat().st_ctime + 30 > current_time.timestamp():
@ -797,20 +798,33 @@ if __name__ == "__main__":
DB_LOCK_FILE.unlink(missing_ok=True)
changes = SCHEDULER.db.check_changes()
changes = SCHEDULER.db.check_changes(with_date=True)
if isinstance(changes, str):
raise Exception(f"An error occurred when checking for changes in the database : {changes}")
if SCHEDULER.db.readonly and changes == old_changes:
continue
# check if the plugins have changed since last time
if changes["pro_plugins_changed"]:
if changes["pro_plugins_changed"] and (
not SCHEDULER.db.readonly
or not changes["last_pro_plugins_change"]
or not old_changes
or old_changes["last_pro_plugins_change"] != changes["last_pro_plugins_change"]
):
logger.info("Pro plugins changed, generating ...")
PRO_PLUGINS_NEED_GENERATION = True
CONFIG_NEED_GENERATION = True
RUN_JOBS_ONCE = True
NEED_RELOAD = True
if changes["external_plugins_changed"]:
if changes["external_plugins_changed"] and (
not SCHEDULER.db.readonly
or not changes["last_external_plugins_change"]
or not old_changes
or old_changes["last_external_plugins_change"] != changes["last_external_plugins_change"]
):
logger.info("External plugins changed, generating ...")
PLUGINS_NEED_GENERATION = True
CONFIG_NEED_GENERATION = True
@ -818,27 +832,44 @@ if __name__ == "__main__":
NEED_RELOAD = True
# check if the custom configs have changed since last time
if changes["custom_configs_changed"]:
if changes["custom_configs_changed"] and (
not SCHEDULER.db.readonly
or not changes["last_custom_configs_change"]
or not old_changes
or old_changes["last_custom_configs_change"] != changes["last_custom_configs_change"]
):
logger.info("Custom configs changed, generating ...")
CONFIGS_NEED_GENERATION = True
CONFIG_NEED_GENERATION = True
NEED_RELOAD = True
# check if the config have changed since last time
if changes["plugins_config_changed"]:
if changes["plugins_config_changed"] and (
not SCHEDULER.db.readonly
or not changes["last_plugins_config_change"]
or not old_changes
or old_changes["plugins_config_changed"] != changes["plugins_config_changed"]
):
logger.info("Plugins config changed, generating ...")
CONFIG_NEED_GENERATION = True
RUN_JOBS_ONCE = True
NEED_RELOAD = True
changed_plugins = changes["plugins_config_changed"]
changed_plugins = list(changes["plugins_config_changed"])
# check if the instances have changed since last time
if changes["instances_changed"]:
if changes["instances_changed"] and (
not SCHEDULER.db.readonly
or not changes["last_instances_change"]
or not old_changes
or old_changes["last_instances_change"] != changes["last_instances_change"]
):
logger.info("Instances changed, generating ...")
INSTANCES_NEED_GENERATION = True
CONFIGS_NEED_GENERATION = True
CONFIG_NEED_GENERATION = True
NEED_RELOAD = True
old_changes = changes.copy()
except BaseException:
logger.debug(format_exc())
if errors > 5:
@ -848,6 +879,8 @@ if __name__ == "__main__":
sleep(5)
if NEED_RELOAD:
logger.debug(f"Changes: {changes}")
SCHEDULER.try_database_readonly(force=True)
CHANGES.clear()
if INSTANCES_NEED_GENERATION: