swap for 2gb upload db
This commit is contained in:
parent
e7874d8e38
commit
afbb8177a3
1 changed files with 64 additions and 22 deletions
|
|
@ -26,14 +26,13 @@ from modules.system.databaseHealth import (
|
||||||
_scanOrphans,
|
_scanOrphans,
|
||||||
)
|
)
|
||||||
from modules.system.databaseMigration import (
|
from modules.system.databaseMigration import (
|
||||||
_buildIdRemapFromPayload,
|
|
||||||
_exportDatabases,
|
_exportDatabases,
|
||||||
_exportSingleDb,
|
_exportSingleDb,
|
||||||
_getAvailableDatabases,
|
_getAvailableDatabases,
|
||||||
_getInstanceLabel,
|
_getInstanceLabel,
|
||||||
_importDatabases,
|
_importDatabases,
|
||||||
_importSingleDb,
|
_importSingleDb,
|
||||||
_loadLiveSystemObjectIds,
|
_prepareImport,
|
||||||
_validateImportPayload,
|
_validateImportPayload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -487,9 +486,10 @@ async def postMigrationUploadImport(
|
||||||
file: UploadFile = File(...),
|
file: UploadFile = File(...),
|
||||||
currentUser: User = Depends(requireSysAdmin),
|
currentUser: User = Depends(requireSysAdmin),
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Upload a backup file to disk (chunked, no full RAM load), validate it,
|
"""Upload a backup file to disk (chunked), validate, remap IDs,
|
||||||
and return a token + metadata for per-DB import.
|
split into per-DB temp files so the full payload doesn't stay in RAM.
|
||||||
"""
|
"""
|
||||||
|
import gc
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -516,7 +516,8 @@ async def postMigrationUploadImport(
|
||||||
os.remove(filePath)
|
os.remove(filePath)
|
||||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Upload failed: {e}") from e
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Upload failed: {e}") from e
|
||||||
|
|
||||||
logger.info("SysAdmin migration upload-import: %s bytes written to disk", totalBytes)
|
logger.info("SysAdmin migration upload-import: %s bytes on disk (%.1f MB)",
|
||||||
|
totalBytes, totalBytes / 1024 / 1024)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(filePath, "r", encoding="utf-8") as f:
|
with open(filePath, "r", encoding="utf-8") as f:
|
||||||
|
|
@ -525,22 +526,41 @@ async def postMigrationUploadImport(
|
||||||
os.remove(filePath)
|
os.remove(filePath)
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid JSON file: {e}") from e
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid JSON file: {e}") from e
|
||||||
|
|
||||||
from modules.system.databaseMigration import _prepareImport
|
try:
|
||||||
|
os.remove(filePath)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
result = _prepareImport(payload)
|
result = _prepareImport(payload)
|
||||||
|
|
||||||
liveIds = _loadLiveSystemObjectIds()
|
if not result.get("valid"):
|
||||||
remap = _buildIdRemapFromPayload(payload, liveIds)
|
del payload
|
||||||
if remap:
|
gc.collect()
|
||||||
logger.info("System-object ID remap: %s", remap)
|
raise HTTPException(
|
||||||
from modules.system.databaseMigration import _remapSystemObjectIds
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
_remapSystemObjectIds(payload, remap)
|
detail={"message": "Payload validation failed", "warnings": result.get("warnings", [])},
|
||||||
|
)
|
||||||
|
|
||||||
protectedIds = list(set(liveIds.values()))
|
protectedIds = result.get("protectedIds", [])
|
||||||
|
|
||||||
|
dbFiles = {}
|
||||||
|
databases = payload.get("databases", {})
|
||||||
|
for dbName, dbData in databases.items():
|
||||||
|
dbPath = os.path.join(tmpDir, f"poweron_import_{token}_{dbName}.json")
|
||||||
|
with open(dbPath, "w", encoding="utf-8") as dbF:
|
||||||
|
json.dump(dbData, dbF, ensure_ascii=False, default=str)
|
||||||
|
dbFiles[dbName] = dbPath
|
||||||
|
|
||||||
|
del payload
|
||||||
|
del databases
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
logger.info("SysAdmin migration upload-import: split into %d per-DB files, payload freed",
|
||||||
|
len(dbFiles))
|
||||||
|
|
||||||
_pendingImports[token] = {
|
_pendingImports[token] = {
|
||||||
"payload": payload,
|
"dbFiles": dbFiles,
|
||||||
"protectedIds": protectedIds,
|
"protectedIds": protectedIds,
|
||||||
"filePath": filePath,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -566,6 +586,8 @@ def postMigrationImportSingle(
|
||||||
|
|
||||||
Body: ``{token, database, mode}``
|
Body: ``{token, database, mode}``
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
token = body.get("token", "")
|
token = body.get("token", "")
|
||||||
database = body.get("database", "")
|
database = body.get("database", "")
|
||||||
mode = body.get("mode", "merge")
|
mode = body.get("mode", "merge")
|
||||||
|
|
@ -577,10 +599,29 @@ def postMigrationImportSingle(
|
||||||
if not pending:
|
if not pending:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired import token.")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired import token.")
|
||||||
|
|
||||||
|
dbFiles = pending.get("dbFiles", {})
|
||||||
|
dbFilePath = dbFiles.get(database)
|
||||||
|
if not dbFilePath or not os.path.exists(dbFilePath):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"No data for database '{database}'.",
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("SysAdmin migration import-single: user=%s db=%s mode=%s", currentUser.username, database, mode)
|
logger.info("SysAdmin migration import-single: user=%s db=%s mode=%s", currentUser.username, database, mode)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = _importSingleDb(pending["payload"], database, mode, pending["protectedIds"])
|
with open(dbFilePath, "r", encoding="utf-8") as f:
|
||||||
|
dbData = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to read import data for '{database}': {e}",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
payload = {"databases": {database: dbData}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = _importSingleDb(payload, database, mode, pending["protectedIds"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Import-single failed for %s: %s", database, e)
|
logger.error("Import-single failed for %s: %s", database, e)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -598,14 +639,15 @@ def postMigrationImportDone(
|
||||||
body: dict,
|
body: dict,
|
||||||
currentUser: User = Depends(requireSysAdmin),
|
currentUser: User = Depends(requireSysAdmin),
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Clean up the server-side payload cache and temp file."""
|
"""Clean up the per-DB temp files."""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
token = body.get("token", "")
|
token = body.get("token", "")
|
||||||
pending = _pendingImports.pop(token, None)
|
pending = _pendingImports.pop(token, None)
|
||||||
if pending and pending.get("filePath"):
|
if pending:
|
||||||
try:
|
for dbPath in pending.get("dbFiles", {}).values():
|
||||||
os.remove(pending["filePath"])
|
try:
|
||||||
except OSError:
|
os.remove(dbPath)
|
||||||
pass
|
except OSError:
|
||||||
|
pass
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue