From bb6dc787f2f72ca77fa4653a290874cf674788f6 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 18 Aug 2021 18:53:45 +0200 Subject: [PATCH] Script migration depuis ScoDoc 7 --- README.md | 58 ++++++++++++++++++ tools/import_scodoc7_dept.py | 33 +++++++--- tools/migrate_from_scodoc7.sh | 112 +++++++++++++++++----------------- tools/restore_scodoc7_data.sh | 98 +++++++++++++++++++++++++++++ tools/save_scodoc7_data.sh | 70 +++++++++++++++++++++ 5 files changed, 308 insertions(+), 63 deletions(-) create mode 100755 tools/restore_scodoc7_data.sh create mode 100755 tools/save_scodoc7_data.sh diff --git a/README.md b/README.md index 82c37365..b68fd65b 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,64 @@ Pour créer un utilisateur "super admin", c'est à dire admin dans tous les dép ## Migration d'une installation ScoDoc 7 +La migration va se faire en suivant les étapes: + + 1. sauvegarder les données de ScoDoc7 (depuis le serveur de production); + + 2. installer le nouveau serveur Linux et ScoDoc 9; + + 3. y charger les données ScoDoc 7; + + 4. importer ces données dans ScoDoc 9. + +### Étape 1: sauvegarde des données du serveur ScoDoc 7 + +Se connecter en tant que `root`sur le serveur ScoDoc 7. + + cd /opt/scodoc/Products/ScoDoc/config + # Mise à jour indispensable pour avoir le script de migration + ./upgrade.sh + # Export des données + ./save_scodoc7_data.sh /tmp/sauvegarde-scodoc7 + +Attention à l'espace disque: au besoin, faire le ménage ou montez un disque supplémentaire. + +Le script indique le nom du fichier à transférer, qui sera dans cet +exemple `/tmp/sauvegarde-scodoc7.tgz` + +Copier ce fichier sur le nouveau serveur. + +### Étape 2 + +Installer le nouveau serveur avec Debian 11 et ScoDoc 9. + +### Étape 3 + +Charger les données ScoDoc 7: en tant qu'utilisateur "`scodoc`" + + cd /opt/scodoc + tools/restore_scodoc7_data.sh /tmp/sauvegarde-scodoc7.tgz + +(adaptez l'argument si les données ont été copiées ailleurs) + +Note: le message +`pg_restore: warning: restoring tables WITH OIDS is not supported anymore` +est normal et anodin. + +A ce stade, vous avez rechargé les bases ScoDoc 7 mais il faut encore +les convertir vers la nouvelle structure ScoDoc 9. + +### Étape 4 + +Importer les données dasn ScoDoc 9: les formats des bases ayant changé +l'opération est complexe et peut durer plusieurs minutes (ou dizaines +de minutes). + + migrate_from_scodoc7.sh /tmp/sauvegarde-scodoc7 + +(le script de l'étape 3 a décompressé l'archive, d'où ici l'absence de l'extension `tgz`). + +### Le script `migrate_from_scodoc7.sh` va déplacer les donneés et reconfigurer les bases de données de votre installation ScoDoc 7 pour passer à ScoDoc 8 (*ne pas utiliser en production !*). **Les modifications effectuées sont sans retour: ScoDoc 7 ne fonctionnera plus !** diff --git a/tools/import_scodoc7_dept.py b/tools/import_scodoc7_dept.py index 248ae817..353e302b 100644 --- a/tools/import_scodoc7_dept.py +++ b/tools/import_scodoc7_dept.py @@ -4,6 +4,7 @@ import inspect import logging import pdb +import time import psycopg2 import sqlalchemy @@ -109,12 +110,16 @@ def import_scodoc7_dept(dept_id: str, dept_db_uri=None): # Utilisateur de rattachement par défaut: default_user = get_super_admin() # + t0 = time.time() for (table, id_name) in SCO7_TABLES_ORDONNEES: logging.info(f"{dept.acronym}: converting {table}...") klass = get_class_for_table(table) + t1 = time.time() n = convert_table(dept, cursor, id_from_scodoc7, klass, id_name, default_user) - logging.info(f" inserted {n} objects.") - logging.info(f"All table imported: clearing app caches...") + logging.info(f" inserted {n} objects in {time.time()-t1}s.") + + logging.info(f"All table imported in {time.time()-t0}s") + logging.info(f"clearing app caches...") clear_scodoc_cache() logging.info(f"Done.") logging.warning(f"Un redémarrage du serveur postgresql est conseillé.") @@ -183,13 +188,11 @@ def convert_table( is_table = True current_id = get_table_max_id(klass) has_id = current_id != -1 - cnx = db.engine.connect() table_name = str(klass.description) boolean_columns = [] else: is_table = False has_id = True - cnx = None table_name = klass.__tablename__ # Colonnes booléennes (valeurs à convertir depuis int) boolean_columns = get_boolean_columns(klass) @@ -200,6 +203,7 @@ def convert_table( current_id = 0 else: current_id = current_id[0] + cnx = db.engine.connect() # mapping: login (scodoc7) : user id (scodoc8) login2id = {u.user_name: u.id for u in User.query} @@ -207,6 +211,7 @@ def convert_table( cursor.execute(f"SELECT * FROM {table_name}") objects = cursor.dictfetchall() + n = 0 for obj in objects: current_id += 1 convert_object( @@ -223,6 +228,11 @@ def convert_table( default_user, cnx, ) + # commit progressif pour ne pas consommer trop de mémoire: + n += 1 + if (not n % 1000) and cnx: + db.session.commit() + if cnx: cnx.close() @@ -244,7 +254,7 @@ def convert_object( boolean_columns=None, login2id=None, default_user=None, - cnx=None, + cnx=None, # cnx à la base destination ): # Supprime l'id ScoDoc7 (eg "formsemestre_id") qui deviendra "id" if id_name: @@ -329,8 +339,9 @@ def convert_object( statement = sqlalchemy.insert(klass).values(**obj) _ = cnx.execute(statement) else: - new_obj = klass(**obj) # ORM object - db.session.add(new_obj) + # new_obj = klass(**obj) # ORM object + # db.session.add(new_obj) + insert_object(cnx, table_name, obj) # Stocke l'id pour les références (foreign keys): if id_name and has_id: @@ -340,6 +351,14 @@ def convert_object( id_from_scodoc7[old_id] = new_id +def insert_object(cnx, table_name: str, vals: dict) -> str: + """insert tuple in db""" + cols = list(vals.keys()) + colnames = ",".join(cols) + fmt = ",".join(["%%(%s)s" % col for col in cols]) + cnx.execute("insert into %s (%s) values (%s)" % (table_name, colnames, fmt), vals) + + # tables ordonnées topologiquement pour les clés étrangères: # g = nx.read_adjlist("misc/model-scodoc7.csv", create_using=nx.DiGraph,delimiter=";") # L = list(reversed(list(nx.topological_sort(g)))) diff --git a/tools/migrate_from_scodoc7.sh b/tools/migrate_from_scodoc7.sh index cc011297..abc5eeff 100755 --- a/tools/migrate_from_scodoc7.sh +++ b/tools/migrate_from_scodoc7.sh @@ -1,25 +1,13 @@ #!/bin/bash -# Migre une install ScoDoc 7 vers ScoDoc 8 -# Le ScoDoc7 est supposé être dans /opt/scodoc7/Products/ScoDoc -# Le nouveau est /opt/scodoc/ +# Migre une install ScoDoc 7 vers ScoDoc 9 +# Les données ScoDoc7 sauvegardées par save_scodoc7_data.sh +# sont copiés au bon endroit +# puis les bases SQL ScoDoc 7 sont traduites dans la base ScoDoc 9 # -# L'install ScoDoc7 de départ doit impérativement être à jour (upgrade.sh). -# -# 1- Utilisateur Unix: -# scodoc7 tournait comme www-data -# scodoc >= 8 tourne sous l'utilisateur unix scodoc -# -# 2- Utilisateur postgresql (bases de données) -# scodoc7 les bases appartenaient à "www-data" -# s'assure que les bases de département (trouvées dans config/dept/*.cfg) -# appartiennent à "scodoc" -# idem pour la base SCOUSERS (avant sa migration vers SCO8USERS) -# -# 3- Fichiers de données et config locale: +# Fichiers de données et config locale: # archives, photos: /opt/scodoc/var/ => /opt/scodoc-data # -# 4- TODO migrer de Apache à nginx, scripts service systemd # set -euo pipefail @@ -30,14 +18,39 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "$SCRIPT_DIR/config.sh" source "$SCRIPT_DIR/utils.sh" -check_uid_root +cd "$SCODOC_DIR" || die "ScoDoc 9 non installe" +source "venv/bin/activate" -SCODOC7_HOME="/opt/scodoc7" -SCODOC7_DIR="${SCODOC7_HOME}/Products/ScoDoc" +# Ce script doit tourner comme "root" +check_uid_root "$0" +# En principe, l'utilisateur "scodoc" existe déjà, mais au cas où on vérifie: +check_create_scodoc_user +# Usage +if [ ! $# -eq 1 ] +then + echo "Usage: $0 directory" + exit 1 +fi + +SCODOC7_HOME="$1" # racine de l'archive importée + +#SCODOC7_HOME="/opt/scodoc7" +#SCODOC7_DIR="${SCODOC7_HOME}/Products/ScoDoc" + +# if [ "${SRC##*.}" == "tgz" ] +# then +# if [ -e ${SRC%%.*} ] +# then +# echo "Error: ${SRC} is an archive but ${SRC%%.*} already exists" +# exit 2 +# fi +# echo "extracting archive ${SRC}..." +# tar xvfz ... # --- 2. Propriétaire des bases de données # Bases appartenant à www-data: +# inutile si on importe via pg_restore (voir restore-scodoc7_data.sh) migrate_database_ownership() { SCO7_BASES=$(su -c "psql -l -t | grep www-data" "$POSTGRES_SUPERUSER" | awk -F '|' '{print $1}') for base in $SCO7_BASES @@ -49,6 +62,7 @@ migrate_database_ownership() { # --- 3. Fichiers locaux: /opt/scodoc/var => /opt/scodoc-data migrate_local_files() { + echo "Déplacement des fichiers de configuration et des archives" SCODOC_VAR_DIR_BACKUP="$SCODOC_VAR_DIR".bak if [ -e "$SCODOC_VAR_DIR_BACKUP" ] then @@ -59,12 +73,19 @@ migrate_local_files() { echo "renomme $SCODOC_VAR_DIR en $SCODOC_VAR_DIR_BACKUP" mv "$SCODOC_VAR_DIR" "$SCODOC_VAR_DIR_BACKUP" fi + mkdir "$SCODOC_VAR_DIR" || die "erreur creation repertoire" mv "${SCODOC7_HOME}"/var/scodoc/* "$SCODOC_VAR_DIR" || die "migrate_local_files failed" + # Anciens logs ScoDoc7 + old_logs_dest="$SCODOC_VAR_DIR/log/scodoc7" + echo "Copie des anciens logs ScoDoc 7 dans $old_logs_dest" + mkdir -p "$old_logs_dest" || die "erreur creation $old_logs_dest" + mv "${SCODOC7_HOME}"/log/* "$old_logs_dest" || die "erreur mv" + # Templates locaux poursuites etudes - if [ -e "${SCODOC7_DIR}"/config/doc_poursuites_etudes/local ] + if [ -e "${SCODOC7_HOME}"/config/doc_poursuites_etudes/local ] then - mv "${SCODOC7_DIR}"/config/doc_poursuites_etudes/local "$SCODOC_VAR_DIR"/config/doc_poursuites_etudes || die "migrate_local_files failed to migrate doc_poursuites_etudes/local" + mv "${SCODOC7_HOME}"/config/doc_poursuites_etudes/local "$SCODOC_VAR_DIR"/config/doc_poursuites_etudes || die "migrate_local_files failed to migrate doc_poursuites_etudes/local" fi # S'assure que le propriétaire est "scodoc": chown -R "${SCODOC_USER}:${SCODOC_GROUP}" "${SCODOC_VAR_DIR}" || die "change_scodoc_file_ownership failed on ${SCODOC_VAR_DIR}" @@ -73,52 +94,31 @@ migrate_local_files() { # ------ MAIN -check_create_scodoc_user +change_scodoc_file_ownership + +migrate_local_files +set_scodoc_var_dir echo -echo -n "Changer si nécessaire le propriétaire des fichiers de $SCODOC_DIR ? (y/n) [y] " -read -r ans -if [ "$(norm_ans "$ans")" != 'N' ] -then - change_scodoc_file_ownership -fi - -echo -echo -n "Changer si nécessaire le propriétaire des bases de données SQL ? (y/n) [y] " -read -r ans -if [ "$(norm_ans "$ans")" != 'N' ] -then - migrate_database_ownership -fi - -echo -echo "Les fichiers locaux de ScoDoc (configuration, photos, procès-verbaux...)" +echo "Les fichiers locaux de ScoDoc: configuration, photos, procès-verbaux..." echo "sont maintenant stockées dans $SCODOC_VAR_DIR" -echo -n "Copier les fichiers locaux de ScoDoc7 ? (y/n) [y] " -read -r ans -if [ "$(norm_ans "$ans")" != 'N' ] -then - migrate_local_files -else - echo -n "Vérifier et créer si besoin le répertoire local /opt/scodoc-data ?" - read -r ans - if [ "$(norm_ans "$ans")" != 'N' ] - then - set_scodoc_var_dir - fi -fi +echo + +echo "XXX STOPPING XXX" +exit 0 # ----- Migration base utilisateurs echo echo "-------------------------------------------------------------" -echo "Importation des utilisateurs de ScoDoc7 dans ScoDoc8 " +echo "Importation des utilisateurs de ScoDoc7 dans ScoDoc 9 " echo "(la base SCOUSERS de ScoDoc7 sera laissée inchangée)" -echo "(les utilisateurs ScoDoc8 existants seront laissés inchangés)" +echo "(les utilisateurs ScoDoc 9 existants seront laissés inchangés)" echo "-------------------------------------------------------------" echo su -c "(cd $SCODOC_DIR && flask import-scodoc7-users)" "$SCODOC_USER" + # ----- Migration bases départements # les départements ScoDoc7 ont été déplacés dans /opt/scodoc-data/config/dept for f in "$SCODOC_VAR_DIR"/config/depts/*.cfg @@ -128,7 +128,7 @@ do echo "----------------------------------------------" echo "| MIGRATION DU DEPARTEMENT $dept" echo "----------------------------------------------" - su -c "(cd $SCODOC_DIR && flask import-scodoc7-dept)" "$dept" + su -c "(cd $SCODOC_DIR && flask import-scodoc7-dept $dept)" "$SCODOC_USER" echo "restarting postgresql server..." systemctl restart postgresql done diff --git a/tools/restore_scodoc7_data.sh b/tools/restore_scodoc7_data.sh new file mode 100755 index 00000000..b6b57cb5 --- /dev/null +++ b/tools/restore_scodoc7_data.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# +# ScoDoc: restore data (saved by save_scodoc7_data) +# into current server +# +# Utile pour migrer de ScoDoc 7 à ScoDoc 9, d'un serveur à un autre +# A executer en tant que root sur le nouveau serveur +# Ce script va créer les base postgresql "scodoc7" (SCOUSERS, SCODEPTs...) +# afin que ScoDoc 9 puisse les importer avec ses scripts de migration. +# Les données (photos, etc) sont pas touchées et seront importées par +# la migration. +# +# E. Viennet, Sept 2011, Nov 2013, Mar 2017, Aug 2020, Jul 2021, Août 21 +# + +# Le répertoire de ce script: +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +source "$SCRIPT_DIR/config.sh" +source "$SCRIPT_DIR/utils.sh" + +if [ "$(id -nu)" != "$SCODOC_USER" ] +then + echo "Erreur: le script $0 doit être lancé par l'utilisateur $SCODOC_USER" +fi + +# Usage +if [ ! $# -eq 1 ] +then + echo "Usage: $0 directory_or_archive" + exit 1 +fi + +SRC=$1 + +if [ "${SRC:0:1}" != "/" ] +then + echo "Usage: $0 directory_or_archive" + echo "Erreur: utiliser un chemin absolu (commencant par /)" + exit 1 +fi + +# Safety check +echo "Ce script recharge les donnees de votre installation ScoDoc 7" +echo "sur ce serveur pour migration vers ScoDoc 9." +echo "Ce fichier doit avoir ete cree par le script save_scodoc_data.sh, sur une autre machine ScoDoc7." +echo +echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]" +read -r ans +if [ ! "$(norm_ans "$ans")" = 'Y' ] +then + echo "Annulation" + exit 1 +fi + +SCOBASES=$(psql -l | grep SCO | grep -v "$SCODOC_DB_PROD" | grep -v "$SCODOC_DB_DEV" | grep -v "$SCODOC_DB_TEST") +if [ -n "$SCOBASES" ] +then + echo "Attention: vous avez apparemment déjà des bases ScoDoc7 chargées" + echo "$SCOBASES" + echo + echo -n "poursuivre quand même ? (y/n) [n]" + read -r ans + if [ ! "$(norm_ans "$ans")" = 'Y' ] + then + echo "Annulation" + exit 1 + fi +fi + + +# Source directory +if [ "${SRC##*.}" = 'tgz' ] +then + echo "Opening tgz archive..." + tmp=$(mktemp -d) + chmod a+rx "$tmp" + cd "$tmp" || terminate "directory error" + tar xfz "$SRC" + SRC=$(ls -1d "$tmp"/*) +fi + +echo "Source is $SRC" + +# Load postgresql dumps +for f in "$SRC"/SCO*.dump +do + echo "Loading postgres database from $f" + pg_restore --create -d SCODOC --no-owner "$f" + if [ ! "$?" -eq 0 ] + then + printf "Error restoring postgresql database\nPlease check that SQL server is running\nAborting." + exit 1 + fi +done + +# \ No newline at end of file diff --git a/tools/save_scodoc7_data.sh b/tools/save_scodoc7_data.sh new file mode 100755 index 00000000..9dc7e6c1 --- /dev/null +++ b/tools/save_scodoc7_data.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# +# ScoDoc: save all user data (database, configs, images, archives...) in separate directory +# +# Utile pour migrer ScoDoc d'un serveur a un autre +# Executer en tant que root sur le serveur d'origine +# +# E. Viennet, Sept 2011, Aug 2020, Jul 2021 +# +# Le répertoire de ce script: +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +source "$SCRIPT_DIR/config.sh" +source "$SCRIPT_DIR/utils.sh" + + +check_uid_root "$0" + +# Destination directory +if [ ! $# -eq 1 ] +then + echo "Usage: $0 destination_directory" + exit 1 +fi +DEST=$1 +# remove trailing slashs if needed: +shopt -s extglob +DEST="${DEST%%+(/)}" + +if [ ! -e "$DEST" ] +then + echo Creating directory "$DEST" + mkdir "$DEST" +else + echo "Error: Directory " "$DEST" " exists" + echo "remove it or specify another destination !" + exit 2 +fi + +echo "Stopping ScoDoc..." +scodocctl stop + +# Dump all postgres databases +echo "Dumping SQL database..." +chown postgres "$DEST" +su -c "pg_dumpall > \"$DEST\"/scodoc.dump.txt" postgres +if [ ! "$?" -eq 0 ] +then + printf "Error dumping postgresql database\nPlease check that SQL server is running\nAborting.\n" + exit 1 +fi +chown root "$DEST" + +# ScoDoc archives, configuration, photos, etc. +echo "Copying var/ ..." +cp -rp "$SCODOC_DIR/var" "$DEST" + + +echo "Copying server logs..." +cp -rp "$SCODOC_DIR/log" "$DEST" + + +# --- Archive all files in a tarball to ease transfer +echo +echo "Archiving backup files in a $DEST.tgz..." +base=$(basename "$DEST") +(cd "$DEST"/.. || terminate "directory error"; tar cfz "$DEST".tgz "$base") + +echo "Done (you can copy " "$DEST"".tgz to destination machine)."