From d2c4ec6de5e6a820e5482d6d953463446b6501c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Fri, 5 Jun 2026 21:11:21 +0200 Subject: [PATCH] [IMP] add --resume-from option to resume the migration process from a step wehre the database and filestore are correct. --- README.md | 50 ++++++++++++++++++- upgrade.sh | 140 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 162 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index bde1687..9371fc3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A tool for migrating Odoo databases between major versions, using [OpenUpgrade]( - [Project Structure](#project-structure) - [How It Works](#how-it-works) - [Usage](#usage) +- [Resuming a Failed Migration](#resuming-a-failed-migration) - [Customization](#customization) - [Troubleshooting](#troubleshooting) @@ -158,7 +159,7 @@ The script performs a **step-by-step migration** between each major version. For ### Running the Migration ```bash -./upgrade.sh +./upgrade.sh [--resume-from|-r ] ``` **Parameters:** @@ -169,6 +170,11 @@ The script performs a **step-by-step migration** between each major version. For | `database_name` | Database name | `my_prod_db` | | `source_service` | Source Docker Compose service | `odoo14` | +**Options:** +| Option | Description | +|--------|-------------| +| `--resume-from `, `-r ` | Resume from an intermediate checkpoint (see [Resuming a Failed Migration](#resuming-a-failed-migration)) | + **Example:** ```bash ./upgrade.sh 14 17 elabore_20241208 odoo14 @@ -239,6 +245,48 @@ compose run odoo17 shell -d ou17 --no-http --stop-after-init < lib/python/valida - **JSON report** written to `/tmp/validation_views__.json` - **Exit code**: `0` = success, `1` = errors found +## Resuming a Failed Migration + +Each version hop copies the database before modifying it (`ou14` → `ou15` → `ou16` → …). If a migration crashes mid-way, the intermediate databases from completed hops are still intact and can be used as a restart point. + +### How It Works + +Use `--resume-from ` (or `-r `) to restart from an intermediate checkpoint: + +```bash +./upgrade.sh --resume-from +``` + +When a checkpoint is specified, the script: +- **Skips** the initial database and filestore copy +- **Skips** `prepare_db.sh` (already done before the crash) +- **Starts the migration loop** from `checkpoint_version + 1` + +### Example + +A migration from 14 to 18 crashes during the 16→17 hop. The database `ou15` was successfully created. Resume from there: + +```bash +./upgrade.sh 14 18 my_database odoo14 --resume-from 15 +``` + +This runs: `16.0 → 17.0 → 18.0`, starting from `ou15`. + +### Constraints + +- The checkpoint version must be strictly between the source and target versions. +- The intermediate database (`ou`) and its filestore must exist before resuming. + +### Restarting From Scratch + +To restart a full migration from the beginning (ignoring all intermediate databases): + +```bash +./upgrade.sh 14 18 my_database odoo14 +``` + +The script automatically drops and recreates the final target database (`ou18`) if it already exists. + ## Customization ### Version Scripts diff --git a/upgrade.sh b/upgrade.sh index 0b2f7a2..6d9cdc1 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -10,7 +10,7 @@ source "${SCRIPT_DIR}/lib/common.sh" usage() { cat <&2 -Usage: $0 +Usage: $0 [--resume-from|-r ] Arguments: origin_version Origin Odoo version number (e.g., 12 for version 12.0) @@ -18,14 +18,22 @@ Arguments: db_name Name of the database to migrate service_name Name of the origin Odoo service (docker compose service) -Example: - $0 14 16 elabore_20241208 odoo14 +Options: + --resume-from, -r + Resume migration from an already-migrated intermediate + database (e.g., ou15). Skips the initial DB copy and + prepare_db.sh phases. The intermediate DB must exist. + +Examples: + $0 14 18 elabore_20241208 odoo14 + $0 14 18 elabore_20241208 odoo14 --resume-from 15 + $0 14 18 elabore_20241208 odoo14 -r 15 EOF exit 1 } if [[ $# -lt 4 ]]; then - log_error "Missing arguments. Expected 4, got $#." + log_error "Missing arguments. Expected at least 4, got $#." usage fi @@ -38,11 +46,50 @@ readonly FINAL_VERSION readonly ORIGIN_DB_NAME="$3" readonly ORIGIN_SERVICE_NAME="$4" +# Parse optional --resume-from / -r flag +RESUME_FROM_VERSION="" +shift 4 +while [[ $# -gt 0 ]]; do + case "$1" in + --resume-from|-r) + if [[ $# -lt 2 ]]; then + log_error "Option '$1' requires a version number argument." + usage + fi + RESUME_FROM_VERSION="$2" + shift 2 + ;; + *) + log_error "Unknown option: '$1'" + usage + ;; + esac +done +readonly RESUME_FROM_VERSION + readonly COPY_DB_NAME="ou${ORIGIN_VERSION}" export FINALE_DB_NAME="ou${FINAL_VERSION}" readonly FINALE_DB_NAME readonly FINALE_SERVICE_NAME="${FINALE_DB_NAME}" +# Validate --resume-from value if provided +if [[ -n "$RESUME_FROM_VERSION" ]]; then + if ! [[ "$RESUME_FROM_VERSION" =~ ^[0-9]+$ ]]; then + log_error "--resume-from value must be a numeric version number (got: '$RESUME_FROM_VERSION')." + exit 1 + fi + if [[ "$RESUME_FROM_VERSION" -le "$ORIGIN_VERSION" ]]; then + log_error "--resume-from ($RESUME_FROM_VERSION) must be strictly greater than origin version ($ORIGIN_VERSION)." + log_error "To start a fresh migration, run without --resume-from." + exit 1 + fi + if [[ "$RESUME_FROM_VERSION" -ge "$FINAL_VERSION" ]]; then + log_error "--resume-from ($RESUME_FROM_VERSION) must be strictly less than final version ($FINAL_VERSION)." + log_error "Nothing to migrate: checkpoint is at or beyond the target version." + exit 1 + fi +fi + readarray -t postgres_containers < <(docker ps --format '{{.Names}}' | grep postgres || true) if [[ ${#postgres_containers[@]} -eq 0 ]]; then @@ -61,8 +108,13 @@ readonly POSTGRES_SERVICE_NAME log_step "INPUT PARAMETERS" log_info "Origin version .......... $ORIGIN_VERSION" log_info "Final version ........... $FINAL_VERSION" -log_info "Origin DB name ........... $ORIGIN_DB_NAME" +log_info "Origin DB name .......... $ORIGIN_DB_NAME" log_info "Origin service name ..... $ORIGIN_SERVICE_NAME" +if [[ -n "$RESUME_FROM_VERSION" ]]; then + log_info "Resume from version ..... $RESUME_FROM_VERSION (ou${RESUME_FROM_VERSION})" +else + log_info "Resume from version ..... none (full migration)" +fi log_step "COMPUTED GLOBAL VARIABLES" log_info "Copy DB name ............. $COPY_DB_NAME" @@ -74,20 +126,42 @@ log_info "Postgres service name .... $POSTGRES_SERVICE_NAME" log_step "CHECKS ALL NEEDED COMPONENTS ARE AVAILABLE" -db_exists=$(docker exec -it -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '$ORIGIN_DB_NAME'" | tr -d '[:space:]') -if [[ "$db_exists" ]]; then - log_info "Database '$ORIGIN_DB_NAME' found." -else - log_error "Database '$ORIGIN_DB_NAME' not found in the local postgres service. Please add it and restart the upgrade process." - exit 1 -fi +if [[ -n "$RESUME_FROM_VERSION" ]]; then + readonly RESUME_DB_NAME="ou${RESUME_FROM_VERSION}" -filestore_path="${DATASTORE_PATH}/${ORIGIN_SERVICE_NAME}/${FILESTORE_SUBPATH}/${ORIGIN_DB_NAME}" -if [[ -d "$filestore_path" ]]; then - log_info "Filestore '$filestore_path' found." + resume_db_exists=$(docker exec -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '${RESUME_DB_NAME}'" | tr -d '[:space:]') + if [[ "$resume_db_exists" ]]; then + log_info "Checkpoint database '${RESUME_DB_NAME}' found." + else + log_error "Checkpoint database '${RESUME_DB_NAME}' not found in the local postgres service." + log_error "Ensure the migration up to version ${RESUME_FROM_VERSION} completed successfully before resuming." + exit 1 + fi + + resume_filestore_path="${DATASTORE_PATH}/${RESUME_DB_NAME}/${FILESTORE_SUBPATH}/${RESUME_DB_NAME}" + if [[ -d "$resume_filestore_path" ]]; then + log_info "Checkpoint filestore '${resume_filestore_path}' found." + else + log_error "Checkpoint filestore '${resume_filestore_path}' not found." + log_error "Ensure the filestore for '${RESUME_DB_NAME}' is intact before resuming." + exit 1 + fi else - log_error "Filestore '$filestore_path' not found, please add it and restart the upgrade process." - exit 1 + db_exists=$(docker exec -u 70 "$POSTGRES_SERVICE_NAME" psql -tc "SELECT 1 FROM pg_database WHERE datname = '$ORIGIN_DB_NAME'" | tr -d '[:space:]') + if [[ "$db_exists" ]]; then + log_info "Database '$ORIGIN_DB_NAME' found." + else + log_error "Database '$ORIGIN_DB_NAME' not found in the local postgres service. Please add it and restart the upgrade process." + exit 1 + fi + + filestore_path="${DATASTORE_PATH}/${ORIGIN_SERVICE_NAME}/${FILESTORE_SUBPATH}/${ORIGIN_DB_NAME}" + if [[ -d "$filestore_path" ]]; then + log_info "Filestore '$filestore_path' found." + else + log_error "Filestore '$filestore_path' not found, please add it and restart the upgrade process." + exit 1 + fi fi log_step "LAUNCH VIRGIN ODOO IN FINAL VERSION" @@ -102,24 +176,36 @@ run_compose --debug run "$FINALE_SERVICE_NAME" -i base --stop-after-init --no-ht log_info "Model database in final Odoo version created." -log_step "COPY ORIGINAL COMPONENTS" +if [[ -z "$RESUME_FROM_VERSION" ]]; then + log_step "COPY ORIGINAL COMPONENTS" -copy_database "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME" -log_info "Original database copied to ${COPY_DB_NAME}@${COPY_DB_NAME}." + copy_database "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME" + log_info "Original database copied to ${COPY_DB_NAME}@${COPY_DB_NAME}." -copy_filestore "$ORIGIN_SERVICE_NAME" "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME" -log_info "Original filestore copied." + copy_filestore "$ORIGIN_SERVICE_NAME" "$ORIGIN_DB_NAME" "$COPY_DB_NAME" "$COPY_DB_NAME" + log_info "Original filestore copied." +else + log_step "COPY ORIGINAL COMPONENTS — SKIPPED (resuming from checkpoint ou${RESUME_FROM_VERSION})" +fi log_step "PATH OF MIGRATION" -readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION") -log_info "Migration path is ${versions[*]}" +if [[ -n "$RESUME_FROM_VERSION" ]]; then + readarray -t versions < <(seq $((RESUME_FROM_VERSION + 1)) "$FINAL_VERSION") + log_info "Resuming migration from ou${RESUME_FROM_VERSION} — path is ${versions[*]}" +else + readarray -t versions < <(seq $((ORIGIN_VERSION + 1)) "$FINAL_VERSION") + log_info "Migration path is ${versions[*]}" +fi -log_step "DATABASE PREPARATION" - -"${SCRIPT_DIR}/scripts/prepare_db.sh" "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME" +if [[ -z "$RESUME_FROM_VERSION" ]]; then + log_step "DATABASE PREPARATION" + "${SCRIPT_DIR}/scripts/prepare_db.sh" "$COPY_DB_NAME" "$COPY_DB_NAME" "$FINALE_DB_NAME" "$FINALE_SERVICE_NAME" +else + log_step "DATABASE PREPARATION — SKIPPED (resuming from checkpoint ou${RESUME_FROM_VERSION})" +fi log_step "UPGRADE PROCESS"