diff --git a/api_server/blueprints/repo_api.py b/api_server/blueprints/repo_api.py new file mode 100755 index 0000000..54f5ed8 --- /dev/null +++ b/api_server/blueprints/repo_api.py @@ -0,0 +1,2124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + API de ogRepository, programada en Flask. + +Responde a peticiones HTTP (en principio, enviadas desde ogCore) mediante endpoints, que a su vez ejecutan los scripts Python almacenados en ogRepository. +En ciertos casos, transforma los parámetros recibidos desde el portal, para adaptarlos a los que es necesario enviar a los scripts + (por ejemplo, a partir del ID de una imagen obtiene su nombre y su extensión). + +Librerías Python requeridas: - flask (se puede instalar con "sudo apt install python3-flask") + - paramiko (se puede instalar con "sudo apt install python3-paramiko") + - requests (se puede instalar con "sudo apt install python3-requests") - No es necesario instalarlo en Ubuntu 24 + - flasgger (se puede instalar con "sudo apt install python3-flasgger") +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +from flask import Flask, jsonify, request +import os +import subprocess +import json +from time import sleep +import paramiko +import logging +import threading +import requests +import random +import hashlib +import psutil +from systemd import journal +# Imports para Swagger: +from flasgger import Swagger +import yaml + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +repo_path = '/opt/opengnsys/ogrepository/images/' # No borrar la barra final +vm_path = '/opt/opengnsys/ogrepository/images_virtual/' # No borrar la barra final +script_path = '/opt/opengnsys/ogrepository/bin' +repo_file = '/opt/opengnsys/ogrepository/etc/repoinfo.json' +trash_file = '/opt/opengnsys/ogrepository/etc/trashinfo.json' +config_file = '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg' + + +# -------------------------------------------------------------------------------------------- +# FUNCTIONS +# -------------------------------------------------------------------------------------------- + + +# Creamos una instancia de la aplicación Flask: +app = Flask(__name__) + +# Cargamos el contenido del archivo "swagger.yaml": +with open("swagger.yaml", "r") as file: + swagger_template = yaml.safe_load(file) + +# Así cambiamos el nombre de la página (por defecto, es 'Flasgger'): +swagger_config = Swagger.DEFAULT_CONFIG +swagger_config['title'] = 'ogRepository API' + +swagger = Swagger(app, template=swagger_template) + + +# --------------------------------------------------------- + + +def get_IPcore(): + """ Obtiene el valor asociado a la variable "IPcore", desde el archivo '/opt/opengnsys/ogrepository/etc/ogAdmRepo.cfg'. + Retorna la IP encontrada (que corresponde a la IP de ogCore), o un error (si no la encuentra). + """ + journal.send("Running function 'get_IPcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + IPcore = None + with open(config_file, 'r') as file: + for line in file: + if line.startswith('IPcore'): + IPcore = line.split('=')[1].strip() + journal.send(f"ogCore IP obtained ({IPcore})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_IPcore', 'desc':'ogCore IP obtained ({IPcore})'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return IPcore + if IPcore is None: + journal.send("Can't obtain ogCore IP", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function get_IPcore', 'desc':'Unable to obtain ogCore IP'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return "IP no encontrada en el archivo de configuración" + + +# --------------------------------------------------------- + + +def get_image_params(image_id, search='all'): + """ A partir de un ID de imagen (que corresponde al "fullsum"), busca la imagen en el repositorio y/o en la papelera (dependiendo del parámetro "search"). + Si encuentra la imagen devuelve su nombre y su extensión en un diccionario, y si no encuentra la imagen especificada retorna "None". + El parámtro "search" tiene el valor predeterminado "all" (que hará que busque tanto en el repo como en la papelera), + pero se le puede pasar el valor "repo" (para que busque solo en el repo) o "trash" (para que busque solo en la papelera). + """ + journal.send("Running function 'get_image_params'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Creamos un diccionario vacío, para almacenar los resultados: + result = {} + + # Abrimos y almacenamos el archivo "repoinfo.json" (solo si se ha de buscar en el repo, y si el archivo tiene contenido): + if (search == 'all' or search == 'repo') and os.path.getsize(repo_file) > 0: + with open(repo_file, 'r') as file: + repo_data = json.load(file) + # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): + for image in repo_data.get('images', []): + if image.get('fullsum') == image_id: + result['name'] = image.get('name') + result['extension'] = image.get('type') + journal.send("Image found in repository JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in repository JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return result + + # Abrimos y almacenamos el archivo "trashinfo.json" (solo si se ha de buscar en la papelera, y si el archivo tiene contenido): + if (search == 'all' or search == 'trash') and os.path.getsize(trash_file) > 0: + with open(trash_file, 'r') as file: + trash_data = json.load(file) + # Iteramos la clave "images" y buscamos la imagen (y si la encontramos almacenamos el nombre y la extension): + for image in trash_data.get('images', []): + if image.get('fullsum') == image_id: + result['name'] = image.get('name') + result['extension'] = image.get('type') + journal.send("Image found in trash JSON file", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function get_image_params', 'desc':'Image found in trash JSON file'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return result + + # Si no encontramos la imagen, retornamos "None": + journal.send("Image not found in JSON file", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function get_image_params', 'desc':'Image not found in JSON file'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return None + + +# --------------------------------------------------------- + + +def search_process(process, string_to_search): + """ Busca procesos que contengan el valor del parámetro "process" y el valor del parámetro "string_to_search" (la ruta de la imagen, normalmente). + Si encuentra alguno retorna "True", y si no encuentra ninguno retorna "False". + """ + journal.send("Running function 'search_process'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + # Obtenemos todos los procesos que están corriendo, y almacenamos la salida y los errores: + result = subprocess.Popen(['ps', '-aux'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + out, error = result.communicate() + + # Almacenamos en una lista los procesos que contengan el proceso del parámetro y la cadena a buscar: + process_list = [line for line in out.split('\n') if process in line and string_to_search in line] + + # Si hemos encontrado algún proceso que cumpla las condiciones, retornamos "True", y si no retornamos "False": + if process_list != []: + journal.send("Process found", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function search_process', 'desc':'Process found'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return True + else: + journal.send("Process not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'operation':'Run function search_process', 'desc':'Process not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return False + # Si se ha producido una excepción, imprimimos el error: + except Exception as error_description: + journal.send(f"Function 'search_process' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function search_process', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + print(f"Unexpected error: {error_description}") + + +# --------------------------------------------------------- + + +def check_remote_connection(remote_ip, remote_user): + """ Comprueba la conexión SSH/SFTP con el servidor remoto que recibe como primer parámetro. + Se utiliza para chequear la conexión antes de importar o exportar una imagen. + """ + journal.send("Running function 'check_remote_connection'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + # Iniciamos un cliente SSH: + ssh_client = paramiko.SSHClient() + # Establecemos la política por defecto para localizar la llave del host localmente: + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Conectamos con el equipo remoto por SSH (con claves): + ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') + + # Iniciamos un cliente SFTP: + sftp_client = ssh_client.open_sftp() + + # Cerramos el cliente SSH y el cliente SFTP: + ssh_client.close() + sftp_client.close() + + # Retornamos "True", porque hemos conseguido conectar: + journal.send("Connection OK", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_connection', 'desc':'Connection OK'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return True + + # Si se produce una excepción, retornamos "False": + except Exception as error_description: + journal.send(f"Function 'check_remote_connection' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_connection', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return False + + +# --------------------------------------------------------- + + +def check_remote_image(remote_ip, remote_user, image_file_path): + """ Conecta con el servidor remoto que recibe como primer parámetro, + para comprobar si la imagen que recibe como tercer parámetro existe, o si está bloqueada. + Se utiliza para chequear la imagen antes de importarla. + """ + journal.send("Running function 'check_remote_image'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Iniciamos un cliente SSH: + ssh_client = paramiko.SSHClient() + # Establecemos la política por defecto para localizar la llave del host localmente: + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Conectamos con el equipo remoto por SSH (con claves): + ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') + + # Iniciamos un cliente SFTP: + sftp_client = ssh_client.open_sftp() + + # Si la imagen no existe, retornamos el mensaje correspondiente: + try: + sftp_client.stat(image_file_path) + except IOError: + return "Remote image not found" + + # Si la imagen existe pero está bloqueada, retornamos el mensaje correspondiente: + try: + sftp_client.stat(f"{image_file_path}.lock") + return "Remote image is locked" + except IOError: + print("Remote image is not locked, as expected") + + # Cerramos el cliente SSH y el cliente SFTP: + ssh_client.close() + sftp_client.close() + + +# --------------------------------------------------------- + + +def check_lock_local(image_file_path, job_id): + """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro, y si existe la imagen y sus archivos asociados. + Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la importación ha sido exitosa, y saldrá de bucle. + Cuando ya no exista el archivo ".lock" (pero tampoco los demás), le comunicará a ogCore que la importación ha fallado, y saldrá de bucle. + Mientras no se cumpla ninguna de las condiciones anteriores, y aun exista el archivo ".lock", seguirá haciendo la comprobación (repitiendo el bucle cada minuto). + """ + journal.send("Running function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 30 segundos, para dar tiempo a que se cree el archivo ".lock": + sleep(30) + + # Creamos un bucle infinito: + while True: + # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle: + if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"): + journal.send("Task finalized (image unlocked)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_lock_local', 'desc':'Image unlocked'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': True + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + # Si no existe el archivo ".lock" (pero tampoco los demás), respondemos a ogCore (con "success: False") y salimos del bucle: + elif not os.path.exists(f"{image_file_path}.lock") and not os.path.exists(image_file_path) and not os.path.exists(f"{image_file_path}.full.sum") and not os.path.exists(f"{image_file_path}.info") and not os.path.exists(f"{image_file_path}.size") and not os.path.exists(f"{image_file_path}.sum") and not os.path.exists(f"{image_file_path}.torrent"): + journal.send("Imported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_lock_local', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': False + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + # Si aun existe el archivo ".lock" (y no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: + elif os.path.exists(f"{image_file_path}.lock"): + journal.send("Task in process (.lock file exists)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 1 minuto para volver a realizar la comprobación: + sleep(60) + + +# --------------------------------------------------------- + + +def check_remote_backup(image_name, remote_ip, remote_user, remote_path, job_id): + """ Cada minuto comprueba si se ha copiado la imagen que recibe como primer parámetro (y sus archivos asociados) al equipo remoto que recibe como segundo parámetro, + y mientras eso no suceda seguirá haciendo la comprobación (en un bucle "while" que se ejecuta cada minuto). + Una vez copiados todos los archivos, chequea la integridad de la imagen, comparando el contenido de los archivos ".sum" y ".size" con los valores reales (que vuelve a calcular). + Si la comprobación es correcta saldrá del bucle, y si es incorrecta saldrá del bucle y borrará los archivos que se hayan copiado al equipo remoto + (y en ambos casos le comunicará a ogCore el resultado, llamando a la función "recall_ogcore"). + """ + journal.send("Running function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Iniciamos un cliente SSH: + ssh_client = paramiko.SSHClient() + # Establecemos la política por defecto para localizar la llave del host localmente: + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + # Conectamos con el equipo remoto por SSH (con claves): + ssh_client.connect(hostname=remote_ip, port=22, username=remote_user, passphrase='') + # Iniciamos un cliente SFTP: + sftp_client = ssh_client.open_sftp() + + # Esperamos 30 segundos antes de empezar a realizar la comprobación: + sleep(30) + + # Creamos una lista con las extensiones de los archivos asociados a la imagen (incluyendo ninguna extensión, que corresponde a la propia imagen): + extensions = ['', '.size', '.sum', '.full.sum', '.info'] + + # Creamos un bucle infinito: + while True: + # Comprobamos si ya se ha copiado la imagen y cada uno de sus archivos asociados (y almacenamos el resultado): + try: + for ext in extensions: + sftp_client.stat(f"{remote_path}{image_name}{ext}") + all_files_copied = True + except IOError: + all_files_copied = False + + # Si se han copiado todos los archivos, comprobamos la integridad de la imagen: + if all_files_copied == True: + journal.send("All files copied", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + # Calculamos el tamaño del la imagen exportada ("size") + file_size = sftp_client.stat(f"{remote_path}{image_name}").st_size + + # Calculamos el hash MD5 del último MB de la imagen exportada ("sum"): + last_mb_offset = max(0, file_size - 1024 * 1024) + with sftp_client.file(f"{remote_path}{image_name}", 'rb') as file: + file.seek(last_mb_offset) + last_mb = file.read(1024 * 1024) + file_sum = hashlib.md5(last_mb).hexdigest() + + # Obtenemos el valor almacenado en el archivo ".size: + with sftp_client.file(f"{remote_path}{image_name}.size", 'r') as file: + stored_size = int(file.read().strip()) + + # Obtenemos el valor almacenado en el archivo ".sum: + with sftp_client.file(f"{remote_path}{image_name}.sum", 'r') as file: + stored_sum = file.read().strip().decode('utf-8') + + except Exception as error: + journal.send(f"Integrity check returned an error: {error}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check returned an error'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + # Borramos los archivos copiados, porque no hemos podido chequear la integridad de la imagen: + remove_remote_files(sftp_client, remote_path, image_name, extensions) + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': False + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + + # Comprobamos si los datos almacenados coinciden con los datos obtenidos (en cuyo caso el backup habrá sido correcto): + if file_sum == stored_sum and file_size == stored_size: + journal.send("Task finalized (backup complete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_remote_backup', 'desc':'Backup complete'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': True + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + else: + journal.send("Exported image didn't pass the Integrity Check", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_remote_backup', 'desc':'Integrity check failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + # Borramos los archivos copiados, porque el chequeo de la integridad de la imagen no ha sido exitoso: + remove_remote_files(sftp_client, remote_path, image_name, extensions) + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': False + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + else: + journal.send("Task in process (backup incomplete)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 1 minuto para volver a realizar la comprobación: + sleep(60) + + # Ya fuera del bucle, cerramos el cliente SSH y el cliente SFTP: + ssh_client.close() + sftp_client.close() + + +# --------------------------------------------------------- + + +def check_aux_files(image_file_path, job_id): + """ Cada 10 segundos comprueba si se han creado todos los archivos auxiliares de la imagen que recibe como parámetro, + en cuyo caso lo comunicará a ogCore, llamando a un endpoint, y dejará de realizar la comprobación. + También obtiene el valor del archivo ".full.sum" (que corresonde al ID), y se lo comunica a ogCore. + """ + journal.send("Running function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Creamos un bucle infinito: + while True: + # Si faltan archivos auxiliares por crear, imprimimos un mensaje en la API: + if not os.path.exists(f"{image_file_path}.size") or not os.path.exists(f"{image_file_path}.sum") or not os.path.exists(f"{image_file_path}.full.sum") or not os.path.exists(f"{image_file_path}.torrent") or not os.path.exists(f"{image_file_path}.info.checked"): + journal.send("Task in process (auxiliar files remaining)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Si ya se han creado todos los archivos auxiliares, imprimimos un mensaje en la API, respondemos a ogCore y salimos del bucle: + else: + journal.send("Task finalized (all auxilar files created)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_aux_files', 'desc':'Auxiliar files created'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Obtenemos el valor del archivo "full.sum", que corresponde al ID: + with open(f"{image_file_path}.full.sum", 'r') as file: + image_id = file.read().strip('\n') + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'image_id': image_id + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, IMAGE_ID: {image_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + # Esperamos 10 segundos para volver a realizar la comprobación: + sleep(10) + + +# --------------------------------------------------------- + + +def check_virtual_image_conversion(image_name, job_id): + """ Cada minuto comprueba si existe la imagen convertida (y sus archivos asociados), o si no existe ninguno de los archivos que se crean en el proceso. + Cuando ya no exista el archivo ".lock" (pero si los demás archivos), le comunicará a ogCore que la conversión ha sido exitosa, y saldrá de bucle. + Cuando no exista ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle. + Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto). + Lo utiliza el endpoint "Convertir imagen virtual a imagen OpenGnsys". + """ + journal.send("Running function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo: + sleep(30) + + # Construimos la ruta de la imagen (una vez convertida): + image_file_path = f"{repo_path}{image_name}.img" + + # Creamos un bucle infinito: + while True: + # Si ya no existe el archivo ".lock" (pero sí existen los demás), respondemos a ogCore (con "success: True") y salimos del bucle: + if not os.path.exists(f"{image_file_path}.lock") and os.path.exists(image_file_path) and os.path.exists(f"{image_file_path}.full.sum") and os.path.exists(f"{image_file_path}.info.checked") and os.path.exists(f"{image_file_path}.size") and os.path.exists(f"{image_file_path}.sum") and os.path.exists(f"{image_file_path}.torrent"): + journal.send("Task finalized (Virtual image converted)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image converted'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': True + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + + # Si no existe ninguno de los archivos que se crean en el proceso (incluyendo la imagen convertida), respondemos a ogCore (con "success: False") y salimos del bucle: + elif not os.path.exists(f"{vm_path}{image_name}.raw") and not os.path.exists(f"{vm_path}{image_name}.img") and not os.path.exists(f"{vm_path}{image_name}.img.lzo") and not os.path.exists(f"{repo_path}{image_name}.img"): + journal.send("Virtual image conversion failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_conversion', 'desc':'Virtual image conversion failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': False + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: + else: + journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 1 minuto para volver a realizar la comprobación: + sleep(60) + + +# --------------------------------------------------------- + + +def check_virtual_image_reconversion(image_name, vm_extension, job_id): + """ Cada minuto comprueba si existe la imagen virtual exportada y si ya no existe ninguno de los archivos que se crean en el proceso (o si ya no existen estos archivos ni la imagen exportada). + En el primer caso, le comunicará a ogCore que la conversión ha sido exitosa y saldrá de bucle, y en el segundo caso, le comunicará a ogCore que la conversión ha fallado, y saldrá de bucle. + Mientras no se cumpla ninguna de las condiciones anteriores, seguirá haciendo la comprobación (repitiendo el bucle cada minuto). + Lo utiliza el endpoint "Convertir imagen OpenGnsys a imagen virtual". + """ + journal.send("Running function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 30 segundos, para dar tiempo a que se cree algún archivo: + sleep(30) + + # Construimos la ruta de la imagen virtual (una vez exportada), y la ruta de exportación: + virtual_image_file_path = f"{vm_path}export/{image_name}.{vm_extension}" + virtual_export_path = f"{vm_path}export/" + + # Creamos un bucle infinito: + while True: + # Si ya no existen los archivos que se crean durante la conversión, pero si la imagen virtual exportada, respondemos a ogCore (con "success: True") y salimos del bucle: + if not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and os.path.exists(virtual_image_file_path): + journal.send("Task finalized (Image converted to virtual)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image converted to virtual'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': True + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: True)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + + # Si no existen los archivos que se crean durante la conversión, pero tampoco la imagen virtual exportada, respondemos a ogCore (con "success: False") y salimos del bucle: + elif not os.path.exists(f"{virtual_export_path}disk.raw") and not os.path.exists(f"{virtual_export_path}{image_name}.img") and not os.path.exists(virtual_image_file_path): + journal.send("Image conversion to virtual failed", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'operation':'Run function check_virtual_image_reconversion', 'desc':'Image conversion to virtual failed'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + # Almacenamos en un diccionario los datos a enviar a ogCore: + data = { + 'job_id': job_id, + 'success': False + } + # Llamamos al endpoint de ogCore, enviando los datos del diccionario: + journal.send(f"Calling function 'recall_ogcore' (JOB_ID: {job_id}, SUCCESS: False)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + recall_ogcore(data) + break + # Si no se han cumplido las condiciones anteriores), imprimimos un mensaje en la API: + else: + journal.send("Task in process (Conversion not finalized)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Esperamos 1 minuto para volver a realizar la comprobación: + sleep(60) + + +# --------------------------------------------------------- + + +def recall_ogcore(data): + """ Hace una petición HTTP de tipo POST a un endpoint de ogCore, enviando datos que dependen del caso. + Se utiliza para informar a ogCore del resultado de una tarea asíncrona, + que estaba corriendo en un proceso independiente (no controlado por los endpoints). + """ + journal.send("Running function 'recall_ogcore'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos la IP de ogCore: + ogcore_ip = get_IPcore() + + # Almacenamos la URL del endpoint de ogCore: + endpoint_url = f"https://{ogcore_ip}:8443/og-repository/webhook" + + # Almacenamos los headers, y convertiomos "data" a JSON: + headers = {'content-type': 'application/json'} + data = json.dumps(data) + + # Hacemos una petición POST al endpoint, enviando lo almacenado en "data": + journal.send("Sending HTTP request...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + response = requests.post(endpoint_url, data=data, headers=headers, verify=False) + + # Imprimimos el código de estado de la petición y la respuesta de ogCore: + journal.send(f"HTTP Status Code: {response.status_code}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + #journal.send(f"HTTP Response: {response.text}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'INFO', 'operation':'Run function recall_ogcore', 'desc':'HTTP Status Code response: {response.status_code}'}}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + +# --------------------------------------------------------- + + +def check_file_exists(file_path): + """ Comprueba la existencia del archivo cuya ruta recibe como parámetro. + Si el archivo existe devuelve "True", y si no devuelve "False". + """ + journal.send("Running function 'check_file_exists'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Comprobamos si existe el archivo de la ruta "file_path": + if os.path.exists(file_path): + return True + else: + return False + + +# --------------------------------------------------------- + + +def remove_remote_files(sftp_client, remote_path, image_name, extensions): + """ Borra la imagen "image_name" y sus archivos asociados (cuyas extensiones están almacenadas en la lista "extensions") + del equipo remoto al que nos hemos conectado previamente (mediante el objeto "sftp_client", que recibe como primer parámetro). + Es llamada por la función "check_remote_backup", cuando la imagen exportada no pasa el test de integridad. + """ + journal.send("Running function 'remove_remote_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Iteramos las extensiones de la lista "extensions", para eliminar la imagen "image_name" y sus archivos asociados: + for ext in extensions: + try: + sftp_client.stat(f"{remote_path}{image_name}{ext}") + sftp_client.remove(f"{remote_path}{image_name}{ext}") + journal.send(f"File with extension {ext} removed", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + except IOError: + journal.send(f"File with extension {ext} doesn't exist", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + +# --------------------------------------------------------- + + +def check_free_space(image_name_full, path): + """ Comprueba si hay suficiente espacio en disco para convertir la imagen que recibe como parámetro + (4 veces el tamaño del archivo), devolviendo "True" si lo hay, y "False" si no lo hay. + Lo utilizan los endpoints "Convertir imagen virtual a imagen OpenGnsys" y "Convertir imagen OpenGnsys a imagen virtual". + """ + # Obtenemos el tamaño de la imagen: + img_size = int(os.path.getsize(f"{path}{image_name_full}")) + # Obtenemos la cantidad de espacio libre en disco: + disk = psutil.disk_usage('/') + free_space = int(disk.free) + + # Si no hay suficiente espacio libre en disco (4 veces el tamaño de la imagen), devolvemos "False", y en caso contrario "True": + if free_space < (img_size * 4): + return False + else: + return True + + + +# -------------------------------------------------------------------------------------------- +# ENDPOINTS +# -------------------------------------------------------------------------------------------- + + +# 1 - Endpoint "Obtener Información de Estado de ogRepository" (SINCRONO): +@app.route("/ogrepository/v1/status", methods=['GET']) +def get_repo_status(): + """ Este endpoint devuelve información de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato json. + Para ello, ejecuta el script "getRepoStatus.py", que no recibe parámetros. + """ + journal.send("Running endpoint 'Obtener Información de Estado de ogRepository'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'getRepoStatus.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "getRepoStatus.py", y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/getRepoStatus.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'getRepoStatus.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoStatus.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + journal.send(f"Script 'getRepoStatus.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoStatus.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'getRepoStatus.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoStatus.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 2 - Endpoint "Obtener Información de todas las Imágenes" (SINCRONO): +@app.route("/ogrepository/v1/images", methods=['GET']) +def get_repo_info(): + """ Este endpoint devuelve información de todas las imágenes contenidas en el repositorio (incluída la papelera), en formato json. + Para ello, ejecuta el script "getRepoInfo.py", con el parámetro "all". + """ + journal.send("Running endpoint 'Obtener Información de todas las Imágenes'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "getRepoInfo.py" (con el parámetro "all"), y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/getRepoInfo.py", 'all'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'getRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + journal.send(f"Script 'getRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'getRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 3 - Endpoint "Obtener Información de una Imagen concreta" (SINCRONO): +@app.route("/ogrepository/v1/images/", methods=['GET']) +def get_repo_image_info(imageId): + """ Este endpoint devuelve información de la imagen especificada como parámetro, en formato json. + Para ello, ejecuta el script "getRepoInfo.py", con el nombre de la imagen como parámetro. + """ + journal.send("Running endpoint 'Obtener Información de una Imagen concreta'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId) + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/getRepoInfo.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint get_repo_image_info', 'desc': 'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + journal.send("Running script 'getRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "getRepoInfo.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'getRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + journal.send(f"Script 'getRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'getRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 4 - Endpoint "Actualizar Información del Repositorio" (SINCRONO): +@app.route("/ogrepository/v1/images", methods=['PUT']) +def update_repo_info(): + """ Este endpoint actualiza la información del repositorio y de la papelera, reflejándola en los archivos "repoinfo.json" y "trashinfo.josn". + Para ello, ejecuta el script "updateRepoInfo.py", que a su vez ejecuta el script "updateTrashInfo.py". + """ + journal.send("Running endpoint 'Actualizar Información del Repositorio'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'updateRepoInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "updateRepoInfo.py" (sin parámetros), y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/updateRepoInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'updateRepoInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script updateRepoInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Repository info updated successfully" + }), 200 + else: + journal.send(f"Script 'updateRepoInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script updateRepoInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'updateRepoInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script updateRepoInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 5 - Endpoint "Chequear Integridad de Imagen" (SINCRONO): +@app.route("/ogrepository/v1/status/images/", methods=['GET']) +def check_image(imageId): + """ Este endpoint comprueba la integridad de la imagen especificada como parámetro, + comparando el tamaño y el hash MD5 del último MB con los valores almacenados en los archivos "size" y "sum". + Para ello, ejecuta el script "checkImage.py", con el nombre de la imagen como único parámetro. + """ + journal.send("Running endpoint 'Chequear Integridad de Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/checkImage.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint check_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found (inexistent or deleted)" + }), 400 + + try: + journal.send("Running script 'checkImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "checkImage.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'checkImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script checkImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + if "Error" in result.stdout: + return jsonify({ + "success": True, + "output": "Image file didn't pass the Integrity Check" + }), 200 + else: + return jsonify({ + "success": True, + "output": "Image file passed the Integrity Check correctly" + }), 200 + else: + journal.send(f"Script 'checkImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script checkImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'checkImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script checkImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 6 - Endpoint "Eliminar una Imagen" (SINCRONO): +@app.route("/ogrepository/v1/images/", methods=['DELETE']) +def delete_image(imageId): + """ Este endpoint elimina la imagen especificada como parámetro (y todos sus archivos asociados), + moviéndolos a la papelera o eliminándolos permanentemente (dependiendo del parámetro "method"). + Para ello, ejecuta el script "deleteImage.py", con el nombre de la imagen como primer parámetro, y el parámetro opcional "-p". + """ + journal.send("Running endpoint 'Eliminar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId, "repo") + # Almacenams el método de eliminación ("trash" o "permanent") + method = request.values.get('method') + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + if method == "permanent": + cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}", '-p'] + elif method == "trash": + cmd = ['python3', f"{script_path}/deleteImage.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Incorrect method (must be 'permanent' or 'trash')", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Incorrect method (must be permanent or trash)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Incorrect method (must be 'permanent' or 'trash')" + }), 400 + else: + journal.send("Image not found (inexistent or deleted)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_image', 'desc':'Warning: Image not found (inexistent or deleted)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found (inexistent or deleted)" + }), 400 + + try: + journal.send("Running script 'deleteImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "deleteImage.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'deleteImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script deleteImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image deleted successfully" + }), 200 + else: + journal.send(f"Script 'deleteImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'deleteImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 7 - Endpoint "Recuperar una Imagen" (SINCRONO): +@app.route("/ogrepository/v1/trash/images", methods=['POST']) +def recover_image(): + """ Este endpoint recupera la imagen especificada como parámetro (y todos sus archivos asociados), + moviéndolos nuevamente al repositorio de imágenes, desde la papelera. + Para ello, ejecuta el script "recoverImage.py", con el nombre de la imagen como único parámetro. + """ + journal.send("Running endpoint 'Recuperar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos el parámetro "ID_img" (enviado por JSON): + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "trash") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/recoverImage.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found (inexistent or recovered previously)", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint recover_image', 'desc':'Warning: Image not found (inexistent or recovered previously)'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found (inexistent or recovered previously)" + }), 400 + + try: + journal.send("Running script 'recoverImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "recoverImage.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'recoverImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script recoverImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image recovered successfully" + }), 200 + else: + journal.send(f"Script 'recoverImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script recoverImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'recoverImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script recoverImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 8 - Endpoint "Eliminar una Imagen de la Papelera" (SINCRONO): +@app.route("/ogrepository/v1/trash/images/", methods=['DELETE']) +def delete_trash_image(imageId): + """ Este endpoint elimina permanentemente la imagen especificada como parámetro (y todos sus archivos asociados), desde la papelera. + Para ello, ejecuta el script "deleteTrashImage.py", con el nombre de la imagen como único parámetro. + """ + journal.send("Running endpoint 'Eliminar una Imagen de la Papelera'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId, "trash") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/deleteTrashImage.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found at trash", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint delete_trash_image', 'desc':'Warning: Image not found at trash'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found at trash" + }), 400 + + try: + journal.send("Running script 'deleteTrashImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "deleteTrashImage.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'deleteTrashImage.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script deleteTrashImage.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image deleted successfully" + }), 200 + else: + journal.send(f"Script 'deleteTrashImage.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteTrashImage.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'deleteTrashImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script deleteTrashImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 9 - Endpoint "Importar una Imagen" (ASINCRONO): +@app.route("/ogrepository/v1/repo/images", methods=['POST']) +def import_image(): + """ Este endpoint importa la imagen especificada como primer parámetro (y todos sus archivos asociados), desde un servidor remoto al servidor local. + Para ello, ejecuta el script "importImage.py", con el nombre de la imagen como primer parámetro, + la IP o hostname del servidor remoto como segundo parámetro, y el usuario con el que conectar al servidor como tercer parámetro. + """ + journal.send("Running endpoint 'Importar una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_name = json_data.get("image") + remote_ip = json_data.get("repo_ip") + remote_user = json_data.get("user") + + # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: + connection_OK = check_remote_connection(remote_ip, remote_user) + if connection_OK == False: + journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Unable to connect to remote server'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Can't connect to remote server" + }), 400 + + # Construimos la ruta de la imagen: + image_file_path = f"{repo_path}{image_name}" + + # Comprobamos si la imagen remota no existe o está bloqueada, en cuyos casos salimos del endpoint y retornamos el error correspondiente: + check_image = check_remote_image(remote_ip, remote_user, image_file_path) + if check_image == "Remote image not found": + journal.send("Remote image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Remote image not found" + }), 400 + elif check_image == "Remote image is locked": + journal.send("Remote image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint import_image', 'desc':'Warning: Remote image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Remote image is locked" + }), 400 + + # Construimos la llamada al script: + cmd = ['python3', f"{script_path}/importImage.py", image_file_path, remote_ip, remote_user] + + try: + journal.send("Running script 'importImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "importImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Generamos el ID para identificar el trabajo asíncrono: + job_id = f"TransferImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" + journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None: + journal.send("Script 'importImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script importImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Si el resultado es correcto, llamamos a la función "check_lock_local" en un hilo paralelo + # (para que compruebe si la imagen se ha acabado de importar exitosamente): + journal.send("Calling function 'check_lock_local'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + threading.Thread(target=check_lock_local, args=(image_file_path, job_id,)).start() + + # Informamos que la imagen se está importando, y salimos del endpoint: + return jsonify({ + "success": True, + "output": "Importing image...", + "job_id": job_id + }), 200 + else: + journal.send("Script 'importImage.py' result KO (Image import failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Error: Image import failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image import failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'importImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exception": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'importImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script importImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 10 - Endpoint "Hacer backup de una Imagen" (ASINCRONO): +@app.route("/ogrepository/v1/repo/images", methods=['PUT']) +def backup_image(): + """ Este endpoint exporta la imagen especificada como primer parámetro (y todos sus archivos asociados), desde el servidor local a un equipo remoto (que no tiene por qué ser un repositorio). + Para ello, ejecuta el script "backupImage.py", con el nombre de la imagen como primer parámetro, la IP o hostname del equipo remoto como segundo parámetro, + el usuario con el que conectar al equipo remoto como tercer parámetro, y la ruta remota en la que copiar la imagen como cuarto parámetro. + """ + journal.send("Running endpoint 'Hacer backup de una Imagen'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + remote_ip = json_data.get("repo_ip") + remote_user = json_data.get("user") + remote_path = json_data.get("remote_path") + + # Si el úitimo carácter de "remote_path" no es una barra, la añadimos: + if remote_path[-1] != "/": + remote_path = f"{remote_path}/" + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir la ruta de la imagen, o para devolver un error si no se ha encontrado la imagen (o si está bloqueada): + if param_dict: + image_name = f"{param_dict['name']}.{param_dict['extension']}" + + # Comprobamos si el archivo que bloquea la imagen existe, llamando a la función "check_file_exists": + image_lock_exists = check_file_exists(f"{repo_path}{image_name}.lock") + + # Si la imagen existe pero está bloqueada, devolvemos un error: + if image_lock_exists == True: + journal.send("Image is locked", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image is locked'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Image is locked" + }), 400 + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + # Comprobamos la conexión con el equipo remoto, y si falla salimos del endpoint, retornando un error: + connection_OK = check_remote_connection(remote_ip, remote_user) + + if connection_OK == False: + journal.send("Can't connect to remote server", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'400', 'operation':'Run endpoint backup_image', 'desc':'Unable to connect to remote host'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Can't connect to remote host" + }), 400 + + # Construimos la llamada al script: + cmd = ['python3', f"{script_path}/backupImage.py", image_name, remote_ip, remote_user, remote_path] + + try: + journal.send("Running script 'backupImage.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "backupImage.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Generamos el ID para identificar el trabajo asíncrono: + job_id = f"BackupImage_{''.join(random.choice('0123456789abcdef') for char in range(8))}" + journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None: + journal.send("Script 'backupImage.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script backupImage.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + # Si el resultado es correcto, llamamos a la función "check_remote_backup" en un hilo paralelo + # (para que compruebe si la imagen se ha acabado de copiar exitosamente): + journal.send("Calling function 'check_remote_backup'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + threading.Thread(target=check_remote_backup, args=(image_name, remote_ip, remote_user, remote_path, job_id,)).start() + + # Informamos que la imagen se está exportando, y salimos del endpoint: + return jsonify({ + "success": True, + "output": "Making image backup...", + "job_id": job_id + }), 200 + else: + journal.send("Script 'backupImage.py' result KO (Backup image failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Backup image failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Backup image failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'backupImage.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Process Exception: {str(error_)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exception": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'backupImage.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script backupImage.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 11 - Endpoint "Crear archivos auxiliares" (ASINCRONO): +@app.route("/ogrepository/v1/images/torrentsum", methods=['POST']) +def create_torrent_sum(): + """ Este endpoint crea los archivos ".size", ".sum", ".full.sum" y ".torrent" para la imagen que recibe como parámetro. + Para ello, ejecuta el script "createTorrentSum.py", con el nombre de la imagen como único parámetro. + """ + journal.send("Running endpoint 'Crear archivos auxiliares'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_name = json_data.get("image") + + # Comprobamos si la imagen existe, llamando a la función "check_file_exists": + image_exists = check_file_exists(f"{repo_path}{image_name}") + + # Si la imagen no existe, retornamos un error y salimos del endpoint: + if image_exists == False: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint create_torrent_sum', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Image not found" + }), 400 + + # Construimos la ruta de la imagen (relativa a "repo_path"): + image_file_path = image_name + + # Construimos la llamada al script: + cmd = ['python3', f"{script_path}/createTorrentSum.py", image_file_path] + + try: + journal.send("Running script 'createTorrentSum.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "createTorrentSum.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Generamos el ID para identificar el trabajo asíncrono: + job_id = f"CreateAuxiliarFiles_{''.join(random.choice('0123456789abcdef') for char in range(8))}" + journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None: + journal.send("Script 'createTorrentSum.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script createTorrentSum.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + # Si el resultado es correcto, llamamos a la función "check_aux_files" en un hilo paralelo + # (para que compruebe si se han creado todos los archivos auxiliares exitosamente): + journal.send("Calling function 'check_aux_files'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + threading.Thread(target=check_aux_files, args=(f"{repo_path}{image_file_path}", job_id,)).start() + + # Informamos que los archivos auxiliares se están creando, y salimos del endpoint: + return jsonify({ + "success": True, + "output": "Creating auxiliar files...", + "job_id": job_id + }), 200 + else: + journal.send(f"Script 'createTorrentSum.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script createTorrentSum.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'createTorrentSum.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script createTorrentSum.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 12 - Endpoint "Enviar paquete Wake On Lan" (SINCRONO): +@app.route("/ogrepository/v1/wol", methods=['POST']) +def send_wakeonlan(): + """ Este endpoint envía un paquete mágico Wake On Lan a la dirección MAC especificada, a través de la IP de broadcast especificadac. + Para ello, ejecuta el script "sendWakeOnLan.py", con la IP de broadcast como primer parámetro, y la MAC como segundo parámetro. + """ + journal.send("Running endpoint 'Enviar paquete Wake On Lan'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + broadcast_ip = json_data.get("broadcast_ip") + mac = json_data.get("mac") + + try: + journal.send("Running script 'sendWakeOnLan.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "sendWakeOnLan.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/sendWakeOnLan.py", broadcast_ip, mac], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'sendWakeOnLan.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Wake On Lan packet sended successfully" + }), 200 + else: + journal.send(f"Script 'sendWakeOnLan.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'sendWakeOnLan.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendWakeOnLan.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 13 - Endpoint "Enviar una Imagen mediante UDPcast" (ASINCRONO): +@app.route("/ogrepository/v1/udpcast", methods=['POST']) +def send_udpcast(): + """ Este endpoint envía mediante UDPcast la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. + Para ello, ejecuta el script "sendFileMcast.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). + """ + journal.send("Running endpoint 'Enviar una Imagen mediante UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + port = json_data.get("port") + method = json_data.get("method") + ip = json_data.get("ip") + bitrate = json_data.get("bitrate") + nclients = json_data.get("nclients") + maxtime = json_data.get("maxtime") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/sendFileMcast.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{method}:{ip}:{bitrate}:{nclients}:{maxtime}"] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + journal.send("Running script 'sendFileMcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "sendFileMcast.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Comprobamos si está corriendo el proceso correspondiente de "udp-sender" (esperando 5 segundos para darle tiempo a iniciarse): + sleep(5) + process_running = search_process('udp-sender', f"{param_dict['name']}.{param_dict['extension']}") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None and process_running == True: + journal.send("Script 'sendFileMcast.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileMcast.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Sending image..." + }), 200 + else: + journal.send("Script 'sendFileMcast.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image send failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'sendFileMcast.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exeption": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'sendFileMcast.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileMcast.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 14 - Endpoint "Enviar una Imagen mediante UFTP" (ASINCRONO): +@app.route("/ogrepository/v1/uftp", methods=['POST']) +def send_uftp(): + """ Este endpoint envía mediante UFTP la imagen que recibe como primer parámetro, con los datos de transferencia que recibe en los demás parámetros. + Para ello, ejecuta el script "sendFileUFTP.py", con la imagen como primer parámetro, y los demás en una cadena (como segundo parámetro). + NOTA: Es necesario que los clientes se hayan puesto en escucha previamente (ejecutando el script "listenUFTPD.py"). + """ + journal.send("Running endpoint 'Enviar una Imagen mediante UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + port = json_data.get("port") + ip = json_data.get("ip") + bitrate = json_data.get("bitrate") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/sendFileUFTP.py", f"{param_dict['name']}.{param_dict['extension']}", f"{port}:{ip}:{bitrate}"] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + journal.send("Running script 'sendFileUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "sendFileUFTP.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Comprobamos si está corriendo el proceso correspondiente de "uftp" (esperando 5 segundos para darle tiempo a iniciarse): + sleep(5) + process_running = search_process('uftp', f"{param_dict['name']}.{param_dict['extension']}") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None and process_running == True: + journal.send("Script 'sendFileUFTP.py' result OK (ReturnCode: None), and process running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script sendFileUFTP.py', 'desc':'Result OK (ReturnCode: None), and process running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Sending image..." + }), 200 + else: + journal.send("Script 'sendFileUFTP.py' result KO (Image send failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Image send failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image send failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'sendFileUFTP.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exeption": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'sendFileUFTP.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script sendFileUFTP.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 15 - Endpoint "Enviar una Imagen mediante P2P" (ASINCRONO): +@app.route("/ogrepository/v1/p2p", methods=['POST']) +def send_p2p(): + """ Este endpoint inicia el tracker "bttrack" y el seeder "bittornado", en el directorio de imágenes (sirviendo todas las imágenes). + Para ello, ejecuta los scripts "runTorrentTracker.py" y "runTorrentSeeder.py", que no reciben parámetros. + """ + journal.send("Running endpoint 'Enviar una Imagen mediante P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir las llamadas a los scripts, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd_tracker = ['sudo', 'python3', f"{script_path}/runTorrentTracker.py"] # Este script si que requiere ser ejecutado con "sudo" + cmd_seeder = ['sudo', 'python3', f"{script_path}/runTorrentSeeder.py"] # Este script si que requiere ser ejecutado con "sudo" + base_path = repo_path.rstrip('/') # Le quito la última barra para poder buscar correctamente en los procesos + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint send_p2p', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + # Ejecutamos los scripts "runTorrentSeeder.py" y "runTorrentSeeder.py", que no reciben parámetros. + # NOTA: No almacenamos la salida ni comprobamos los errores, porque los procesos quedarán corriendo hasta que se finalicen manualmente, + # por lo que no podemos comprobar el returncode (luego comprobaremos si los procesos se han iniciado correctamente). + journal.send("Running script 'runTorrentTracker.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + subprocess.Popen(cmd_tracker) + + journal.send("Running script 'runTorrentSeeder.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + subprocess.Popen(cmd_seeder) + + # Comprobamos si el tracker y el seeder están corriendo, y si apuntan al directorio que le hemos pasado + # (esperamos 10 segundos antes de hacerlo, porque los procesos no se inician inmediatamente): + sleep(10) + tracker_running = search_process('bttrack', base_path) + seeder_running = search_process('btlaunchmany', base_path) + + # Evaluamos las comprobaciones anteriores, para devolver la respuesta que corresponda: + if tracker_running and seeder_running: + journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results OK (ReturnCodes: None), and processes running", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results OK (ReturnCodes: None), and processes running'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Tracker and Seeder serving image correctly" + }), 200 + else: + journal.send("Scripts 'runTorrentTracker.py' and 'runTorrentSeeder.py' results KO (Tracker or/and Seeder not runnig)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run scripts runTorrentTracker.py and runTorrentSeeder.py', 'desc':'Results KO (Tracker or/and Seeder not runnig)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Tracker or Seeder (or both) not running" + }), 500 + + +# --------------------------------------------------------- + + +# 16 - Endpoint "Ver Estado de Transmisiones UDPcast" (SINCRONO): +@app.route("/ogrepository/v1/udpcast", methods=['GET']) +def get_udpcast_info(): + """ Este endpoint devuelve información sobre los procesos de "udp-sender" activos, en formato json, + lo que en la práctica permite comprobar las transferencias UDPcast activas. + Para ello, ejecuta el script "getUDPcastInfo.py", que no recibe parámetros. + """ + journal.send("Running endpoint 'Ver Estado de Transmisiones UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'getUDPcastInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "getUDPcastInfo.py", y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/getUDPcastInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'getUDPcastInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + journal.send(f"Script 'getUDPcastInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 1" in str(error_description): + journal.send("No UDPcast active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUDPcastInfo.py', 'desc':'Warning: No UDPcast active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "No UDPCast active transmissions" + }), 400 + else: + journal.send(f"Script 'getUDPcastInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUDPcastInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 17 - Endpoint "Ver Estado de Transmisiones UFTP" (SINCRONO): +@app.route("/ogrepository/v1/uftp", methods=['GET']) +def get_uftp_info(): + """ Este endpoint devuelve información sobre los procesos de "uftp" activos, en formato json, + lo que en la práctica permite comprobar las transferencias UFTP activas. + Para ello, ejecuta el script "getUFTPInfo.py", que no recibe parámetros. + """ + journal.send("Running endpoint 'Ver Estado de Transmisiones UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'getUFTPInfo.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "getUFTPInfo.py", y almacenamos el resultado: + result = subprocess.run(['python3', f"{script_path}/getUFTPInfo.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'getUFTPInfo.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script getUFTPInfo.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + journal.send(f"Script 'getUFTPInfo.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUFTPInfo.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 1" in str(error_description): + journal.send("No UFTP active transmissions", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script getUFTPInfo.py', 'desc':'Warning: No UFTP active transmissions'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "No UFTP active transmissions" + }), 400 + else: + journal.send(f"Script 'getUFTPInfo.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script getUFTPInfo.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 18 - Endpoint "Cancelar Transmisión UDPcast" (SINCRONO): +@app.route("/ogrepository/v1/udpcast/images/", methods=['DELETE']) +def stop_udpcast(imageId): + """ Este endpoint cancela la transmisión UDPcast de la imagen que recibe como parámetro, finalizando el proceso "udp-sender" asociado. + Para ello, ejecuta el script "stopUDPcast.py", pasándole el nombre de la imagen. + """ + journal.send("Running endpoint 'Cancelar Transmisión UDPcast'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/stopUDPcast.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint stop_udpcast', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + journal.send("Running script 'stopUDPcast.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "stopUDPcast.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'stopUDPcast.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopUDPcast.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image transmission canceled successfully" + }), 200 + else: + journal.send(f"Script 'stopUDPcast.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 3" in str(error_description): + journal.send("No UDPCast active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUDPcast.py', 'desc':'Warning: No UDPCast active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "No UDPCast active transmissions for specified image" + }), 400 + elif "exit status 4" in str(error_description): + journal.send("Script 'stopUDPcast.py' result KO (Unexpected error checking UDPcast transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error checking UDPcast transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Unexpected error checking UDPcast transmissions" + }), 500 + elif "exit status 5" in str(error_description): + journal.send("Script 'stopUDPcast.py' result KO (Unexpected error finalizing UDPcast transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Unexpected error finalizing UDPcast transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Unexpected error finalizing UDPcast transmission" + }), 500 + else: + journal.send(f"Script 'stopUDPcast.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUDPcast.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 19 - Endpoint "Cancelar Transmisión UFTP" (SINCRONO): +@app.route("/ogrepository/v1/uftp/images/", methods=['DELETE']) +def stop_uftp(imageId): + """ Este endpoint cancela la transmisión UFTP de la imagen que recibe como parámetro, finalizando el proceso "uftp" asociado. + Para ello, ejecuta el script "stopUFTP.py", pasándole el nombre de la imagen. + """ + journal.send("Running endpoint 'Cancelar Transmisión UFTP'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(imageId, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + cmd = ['python3', f"{script_path}/stopUFTP.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint stop_uftp', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + journal.send("Running script 'stopUFTP.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "stopUFTP.py" (con los parámetros almacenados), y almacenamos el resultado: + result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode == 0: + journal.send("Script 'stopUFTP.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopUFTP.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "Image transmission canceled successfully" + }), 200 + else: + journal.send(f"Script 'stopUFTP.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 3" in str(error_description): + journal.send("No UFTP active transmissions for specified image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run script stopUFTP.py', 'desc':'Warning: No UFTP active transmissions for specified image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "No UFTP active transmissions for specified image" + }), 400 + elif "exit status 4" in str(error_description): + journal.send("Script 'stopUFTP.py' result KO (Unexpected error checking UFTP transmissions)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error checking UFTP transmissions)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Unexpected error checking UFTP transmissions" + }), 500 + elif "exit status 5" in str(error_description): + journal.send("Script 'stopUFTP.py' result KO (Unexpected error finalizing UFTP transmission)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Unexpected error finalizing UFTP transmission)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Unexpected error finalizing UFTP transmission" + }), 500 + else: + journal.send(f"Script 'stopUFTP.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopUFTP.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 20 - Endpoint "Cancelar Transmisiones P2P" (SINCRONO): +@app.route("/ogrepository/v1/p2p", methods=['DELETE']) +def stop_p2p(): + """ Este endpoint cancela las transmisiones P2P activas, finalizando los procesos "btlaunchmany.bittornado" (seeder) y "bttrack" (tracker). + Para ello, ejecuta el script "stopP2P.py", que no recibe parámetros. + """ + journal.send("Running endpoint 'Cancelar Transmisiones P2P'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + try: + journal.send("Running script 'stopP2P.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "stopP2P.py", y almacenamos el resultado (este script si que requiere ser ejecutado con "sudo"): + result = subprocess.run(['sudo', 'python3', f"{script_path}/stopP2P.py"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Evaluamos el resultado de la ejecución, y devolvemos la respuesta: + if result.returncode == 0: + journal.send("Script 'stopP2P.py' result OK (ReturnCode: 0)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script stopP2P.py', 'desc':'Result OK (ReturnCode: 0)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": True, + "output": "P2P transmissions canceled successfully" + }), 200 + else: + journal.send(f"Script 'stopP2P.py' result KO (Error: {result.stderr})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopP2P.py', 'desc':'Result KO (Error: {result.stderr})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + journal.send(f"Script 'stopP2P.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script stopP2P.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 21 - Endpoint "Convertir imagen virtual a imagen OpenGnsys" (ASINCRONO): +@app.route("/ogrepository/v1/images/virtual", methods=['POST']) +def convert_virtual_image(): + """ Este endpoint convierte la imagen virtual especificada como parámetro en una imagen "img" como las que se generan desde OpenGnsys + (con "partclone" y "lzop"), por lo que luego puede ser restaurada como cualquier otra imagen del repositorio. + Para ello, ejecuta el script "convertVMtoIMG.py", con el nombre de la imagen virtual como primer parámetro, + y el sistema de archivos de la partición a clonar (en formato "blkid") como segundo parámetro. + """ + journal.send("Running endpoint 'Convertir imagen virtual a imagen OpenGnsys'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON, y extraemos el nombre y la extensión: + json_data = json.loads(request.data) + vm_image_name_full = json_data.get("virtual_image") + vm_image_name = vm_image_name_full.split('.')[0] + vm_extension = vm_image_name_full.split('.')[1] + filesystem = json_data.get("filesystem").lower() + + # Comprobamos si existe la imagen virtual, llamando a la función "check_file_exists": + vm_image_exists = check_file_exists(f"{vm_path}{vm_image_name_full}") + + # Si la imagen virtual no existe, devolvemos un error: + if vm_image_exists == False: + journal.send("Virtual image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: Virtual image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "Virtual image not found" + }), 400 + + # Comprobamos si ya existe una imagen "img" con el mismo nombre que la imagen virtual, llamando a la función "check_file_exists": + img_image_exists = check_file_exists(f"{repo_path}{vm_image_name}.img") + + # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error: + if img_image_exists == True: + journal.send("There is an image with the same name as the virtual image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is an image with the same name as the virtual image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "There is an image with the same name as the virtual image" + }), 400 + + # Comprobamos si hay espacio suficiente en disco para convertir la imagen virtual (4 veces su tamaño): + enough_free_space = check_free_space(vm_image_name_full, vm_path) + + # Si no hay suficiente espacio libre en disco, devolvemos un error: + if enough_free_space == False: + journal.send("There is not enough free disk space", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_virtual_image', 'desc':'Warning: There is not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "There is not enough free disk space" + }), 400 + + # Construimos la llamada al script: + cmd = ['python3', f"{script_path}/convertVMtoIMG.py", vm_image_name_full, filesystem] + + try: + journal.send("Running script 'convertVMtoIMG.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "convertVMtoIMG.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Generamos el ID para identificar el trabajo asíncrono: + job_id = f"ConvertImageFromVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}" + journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None: + journal.send("Script 'convertVMtoIMG.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Si el resultado es correcto, llamamos a la función "check_virtual_image_conversion" en un hilo paralelo + # (para que compruebe si la imagen se ha acabado de convertir exitosamente): + journal.send("Calling function 'check_virtual_image_conversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + threading.Thread(target=check_virtual_image_conversion, args=(vm_image_name, job_id,)).start() + + # Informamos que la imagen se está convirtiendo, y salimos del endpoint: + return jsonify({ + "success": True, + "output": "Converting virtual image...", + "job_id": job_id + }), 200 + else: + journal.send("Script 'convertVMtoIMG.py' result KO (Virtual image conversion failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Error: Virtual image conversion failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Virtual image conversion failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'convertVMtoIMG.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exception": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'convertVMtoIMG.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertVMtoIMG.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 22 - Endpoint "Convertir imagen OpenGnsys a imagen virtual" (ASINCRONO): +@app.route("/ogrepository/v1/images/virtual", methods=['PUT']) +def convert_image_to_virtual(): + """ Este endpoint convierte la imagen de OpenGnsys especificada como parámetro en una imagen virtual con la extensión especificada (".vdi", ".vmdk", etc). + Para ello, ejecuta el script "convertIMGtoVM.py", con el nombre de la imagen "img" como primer parámetro, + y la extensión del disco virtual destino (sin punto) como segundo parámetro. + """ + journal.send("Running endpoint 'Convertir imagen OpenGnsys a imagen virtual'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Almacenamos los parámetros enviados en el JSON,: + json_data = json.loads(request.data) + image_id = json_data.get("ID_img") + vm_extension = json_data.get("vm_extension").lower().lstrip('.') + + # Obtenemos el nombre y la extensión de la imagen: + param_dict = get_image_params(image_id, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devolver un error si no se ha encontrado la imagen: + if param_dict: + image_name_full = f"{param_dict['name']}.{param_dict['extension']}" + image_name = f"{param_dict['name']}" + cmd = ['python3', f"{script_path}/convertIMGtoVM.py", image_name_full, vm_extension] + else: + journal.send("Image not found", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: Image not found'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + # Comprobamos si ya existe una imagen virtual exportada con el mismo nombre que la imagen "img" y la extensión especificada, llamando a la función "check_file_exists": + vm_image_exists = check_file_exists(f"{vm_path}export/{image_name}.{vm_extension}") + + # Si existe una imagen con el mismo nombre que la imagen virtual (salvo por la extensión), devolvemos un error: + if vm_image_exists == True: + journal.send("There is an exported virtual image with the same name as the image", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: There is an exported virtual image with the same name as the image'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "There is an exported virtual image with the same name as the image" + }), 400 + + # Comprobamos si hay espacio suficiente en disco para convertir la imagen "img" a virtual (4 veces su tamaño): + enough_free_space = check_free_space(image_name_full, repo_path) + + # Si no hay suficiente espacio libre en disco, devolvemos un error: + if enough_free_space == False: + journal.send("There is not enough free disk space", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'WARNING', 'http_code':'400', 'operation':'Run endpoint convert_image_to_virtual', 'desc':'Warning: There is not enough free disk space'}", PRIORITY=journal.LOG_WARNING, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": "There is not enough free disk space" + }), 400 + + try: + journal.send("Running script 'convertIMGtoVM.py'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + # Ejecutamos el script "convertIMGtoVM.py" (con los parámetros almacenados), y almacenamos el resultado (pero sin esperar a que termine el proceso): + result = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + + # Generamos el ID para identificar el trabajo asíncrono: + job_id = f"ConvertImageToVirtual_{''.join(random.choice('0123456789abcdef') for char in range(8))}" + journal.send(f"JOB ID generated ({job_id})", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + + # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: + if result.returncode is None: + journal.send("Script 'convertIMGtoVM.py' result OK (ReturnCode: None)", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'INFO', 'http_code':'200', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result OK (ReturnCode: None)'}", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api") + + # Si el resultado es correcto, llamamos a la función "check_virtual_image_reconversion" en un hilo paralelo + # (para que compruebe si la imagen se ha acabado de convertir exitosamente): + journal.send("Calling function 'check_virtual_image_reconversion'...", PRIORITY=journal.LOG_INFO, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + threading.Thread(target=check_virtual_image_reconversion, args=(image_name, vm_extension, job_id,)).start() + + # Informamos que la imagen se está convirtiendo, y salimos del endpoint: + return jsonify({ + "success": True, + "output": "Converting image to virtual...", + "job_id": job_id + }), 200 + else: + journal.send("Script 'convertIMGtoVM.py' result KO (Image conversion to virtual failed)", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send("{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Error: Image conversion to virtual failed)'}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "error": "Image conversion to virtual failed" + }), 500 + except subprocess.CalledProcessError as error: + journal.send(f"Script 'convertIMGtoVM.py' result KO (Process Exception: {str(error)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Process Exception: {str(error)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "process exception": str(error) + }), 500 + except Exception as error_description: + journal.send(f"Script 'convertIMGtoVM.py' result KO (Exception: {str(error_description)})", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api_DEBUG") + journal.send(f"{{'component':'ogRepo', 'severity':'ERROR', 'http_code':'500', 'operation':'Run script convertIMGtoVM.py', 'desc':'Result KO (Exception: {str(error_description)})'}}", PRIORITY=journal.LOG_ERR, SYSLOG_IDENTIFIER="ogrepo-api") + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + + +# -------------------------------------------------------------------------------------------- + + +# Ejecutamos la aplicación, en el puerto "8006": +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=8006) + + +# --------------------------------------------------------------------------------------------