diff --git a/api/repo_api.py b/api/repo_api.py index fe80c9c..fd7c710 100644 --- a/api/repo_api.py +++ b/api/repo_api.py @@ -8,7 +8,10 @@ Responde a peticiones HTTP (en principio, enviadas desde ogCore) mediante endpoi 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, su extensión y el subdirectorio de OU). -Librerías Python requeridas: Flask (se puede instalar con "sudo apt install python3-flask"). +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 tengo claro que para este paquete sea necesario + - "flasgger" (se puede instalar con "sudo apt install python3-flasgger") """ # -------------------------------------------------------------------------------------------- @@ -20,8 +23,15 @@ import os import subprocess import json from time import sleep +import paramiko +import logging +import threading +import requests +# Imports para Swagger: from flasgger import Swagger import yaml + + # -------------------------------------------------------------------------------------------- # VARIABLES # -------------------------------------------------------------------------------------------- @@ -31,6 +41,12 @@ script_path = '/opt/opengnsys/bin' repo_file = '/opt/opengnsys/etc/repoinfo.json' trash_file = '/opt/opengnsys/etc/trashinfo.json' +""" +repo_path = '/home/user/images/' +script_path = '/home/user' +repo_file = '/home/user/jsons/repoinfo.json' +trash_file = '/home/user/jsons/trashinfo.json' +""" # -------------------------------------------------------------------------------------------- # FUNCTIONS @@ -40,9 +56,14 @@ trash_file = '/opt/opengnsys/etc/trashinfo.json' # Creamos una instancia de la aplicación Flask: app = Flask(__name__) + +# Configuración Swagger: with open("swagger.yaml", "r") as file: swagger_template = yaml.safe_load(file) + swagger = Swagger(app, template=swagger_template) + + # --------------------------------------------------------- @@ -123,13 +144,104 @@ def search_process(process, string_to_search): print(f"Unexpected error: {error_description}") +# --------------------------------------------------------- + + +def check_lock_local(image_file_path): + """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro + (lo que significará que hay una tarea en curso), en el repositorio local. + Cuando no encuentre el archivo ".lock" lo comunicará a ogCore, llamando a un endpoint, + y dejará de realizar la comprobación (saliendo del bucle). + """ + # 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", imprimimos un mensaje en la API, y salimos del bucle: + if not os.path.exists(f"{image_file_path}.lock"): + app.logger.info("Task finalized (no .lock file)") # De momento solamente imprimimos un mensaje en la API (pero debe llamar a un endpoint) + break + # Si aun existe el archivo ".lock", imprimimos un mensaje en la API: + else: + app.logger.info("Task in process (.lock file exists)") + # Esperamos 1 minuto para volver a realizar la comprobación: + sleep(60) + + +# --------------------------------------------------------- + + +def check_lock_remote(image_file_path, remote_host, remote_user): + """ Cada minuto comprueba si existe un archivo ".lock" asociado a la imagen que recibe como parámetro + (lo que significará que hay una tarea en curso), en un repositorio remoto (al que conecta por SSH/SFTP). + Cuando no encuentre el archivo ".lock" lo comunicará a ogCore, llamando a un endpoint, + y dejará de realizar la comprobación (saliendo del bucle). + """ + # 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: + ssh_client.connect(remote_host, 22, remote_user) # Así se hace con claves + #ssh_client.connect(remote_host, 22, remote_user, 'opengnsys') # Así se haría con password + + # Iniciamos un cliente SFTP: + sftp_client = ssh_client.open_sftp() + # Esperamos 30 segundos, para dar tiempo a que se cree el archivo ".lock": + sleep(30) + + # Creamos un bucle infinito: + while True: + try: + # Si aun existe el archivo ".lock", imprimimos un mensaje en la API: + sftp_client.stat(f"{image_file_path}.lock") + app.logger.info("Task in process (.lock file exists)") + except IOError: + # Si ya no existe el archivo ".lock", imprimimos un mensaje en la API, y salimos del bucle: + app.logger.info("Task finalized (no .lock file)") # De momento solamente imprimimos un mensaje en la API (pero debe llamar a un endpoint) + break + # 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): + """ 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. + """ + # 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"): + app.logger.info("Task in process (auxiliar files remaining)") + # Si ya se han creado todos los archivos auxiliares, imprimimos un mensaje en la API, y salimos del bucle: + else: + app.logger.info("Task finalized (all auxilar files created)") # De momento solamente imprimimos un mensaje en la API (pero debe llamar a un endpoint) + # Obtenemos el valor del archivo "full.sum", que corresponde al ID, y lo imprimimos: + with open(f"{image_file_path}.full.sum", 'r') as file: + image_id = file.read().strip('\n') + app.logger.info(f"Image_ID: {image_id}") + break + # Esperamos 10 segundos para volver a realizar la comprobación: + sleep(10) + + # -------------------------------------------------------------------------------------------- # ENDPOINTS # -------------------------------------------------------------------------------------------- -# 1 - Endpoint "Obtener Información de Estado de ogRepository": +# 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. @@ -160,7 +272,7 @@ def get_repo_status(): # --------------------------------------------------------- -# 2 - Endpoint "Obtener Información de todas las Imágenes": +# 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. @@ -191,7 +303,7 @@ def get_repo_info(): # --------------------------------------------------------- -# 3 - Endpoint "Obtener Información de una Imagen concreta": +# 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. @@ -238,7 +350,7 @@ def get_repo_image_info(imageId): # --------------------------------------------------------- -# 4 - Endpoint "Actualizar Información del Repositorio": +# 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". @@ -269,7 +381,7 @@ def update_repo_info(): # --------------------------------------------------------- -# 5 - Endpoint "Chequear Integridad de Imagen": +# 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, @@ -323,7 +435,7 @@ def check_image(imageId): # --------------------------------------------------------- -# 6 - Endpoint "Eliminar una Imagen": +# 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), @@ -379,7 +491,7 @@ def delete_image(imageId): # --------------------------------------------------------- -# 7 - Endpoint "Recuperar una Imagen": +# 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), @@ -431,7 +543,7 @@ def recover_image(): # --------------------------------------------------------- -# 8 - Endpoint "Eliminar una Imagen de la Papelera": +# 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. @@ -478,7 +590,7 @@ def delete_trash_image(imageId): # --------------------------------------------------------- -# 9 - Endpoint "Importar una Imagen": +# 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), @@ -493,26 +605,39 @@ def import_image(): remote_ip = json_data.get("repo_ip") remote_user = json_data.get("user") - # Evaluamos los parámetros obtenidos, para construir la llamada al script: + # Evaluamos los parámetros obtenidos, para construir la ruta de la imagen: if ou_subdir == "none": - cmd = ['sudo', 'python3', f"{script_path}/importImage.py", f"{repo_path}{image_name}", remote_ip, remote_user] + image_file_path = f"{repo_path}{image_name}" else: - cmd = ['sudo', 'python3', f"{script_path}/importImage.py", f"{repo_path}{ou_subdir}/{image_name}", remote_ip, remote_user] + image_file_path = f"{repo_path}{ou_subdir}/{image_name}" + + # Construimos la llamada al script: + cmd = ['sudo', 'python3', f"{script_path}/importImage.py", image_file_path, remote_ip, remote_user] try: - # Ejecutamos el script "importImage.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + # 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') # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: + if result.returncode is None: + # 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): + threading.Thread(target=check_lock_local, args=(image_file_path,)).start() + + # Informamos que la imagen se está importando, y salimos del endpoint: return jsonify({ "success": True, - "output": "Image imported successfully" + "output": "Importing image..." }), 200 else: return jsonify({ "success": False, - "error": result.stderr + "error": "Image import failed" + }), 500 + except subprocess.CalledProcessError as error: + return jsonify({ + "success": False, + "process exception": str(error) }), 500 except Exception as error_description: if "exit status 2" in str(error_description): @@ -540,7 +665,7 @@ def import_image(): # --------------------------------------------------------- -# 10 - Endpoint "Exportar una Imagen": +# 10 - Endpoint "Exportar una Imagen" (ASINCRONO): @app.route("/ogrepository/v1/repo/images", methods=['PUT']) def export_image(): """ Este endpoint exporta la imagen especificada como primer parámetro (y todos sus archivos asociados), @@ -557,33 +682,46 @@ def export_image(): # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): param_dict = get_image_params(image_id, "repo") - # Evaluamos los parámetros obtenidos, para construir la llamada al script, o para devover un error si no se ha encontrado la imagen: + # Evaluamos los parámetros obtenidos, para construir la ruta de la imagen, o para devover un error si no se ha encontrado la imagen: if param_dict: if 'subdir' in param_dict: - cmd = ['sudo', 'python3', f"{script_path}/exportImage.py", f"{param_dict['subdir']}/{param_dict['name']}.{param_dict['extension']}", remote_ip, remote_user] + image_file_path = f"{param_dict['subdir']}/{param_dict['name']}.{param_dict['extension']}" else: - cmd = ['sudo', 'python3', f"{script_path}/exportImage.py", f"{param_dict['name']}.{param_dict['extension']}", remote_ip, remote_user] + image_file_path = f"{param_dict['name']}.{param_dict['extension']}" else: return jsonify({ "success": False, "error": "Image not found" }), 400 + # Construimos la llamada al script: + cmd = ['sudo', 'python3', f"{script_path}/exportImage.py", image_file_path, remote_ip, remote_user] + try: - # Ejecutamos el script "exportImage.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + # Ejecutamos el script "exportImage.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') # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: + if result.returncode is None: + # Si el resultado es correcto, llamamos a la función "check_lock_remote" en un hilo paralelo + # (para que compruebe si la imagen se ha acabado de exportar exitosamente): + threading.Thread(target=check_lock_remote, args=(f"{repo_path}{image_file_path}", remote_ip, remote_user,)).start() + + # Informamos que la imagen se está exportando, y salimos del endpoint: return jsonify({ "success": True, - "output": "Image exported successfully" + "output": "Exporting image" }), 200 else: return jsonify({ "success": False, "error": result.stderr }), 500 + except subprocess.CalledProcessError as error: + return jsonify({ + "success": False, + "process exception": str(error) + }), 500 except Exception as error_description: if "exit status 3" in str(error_description): return jsonify({ @@ -610,7 +748,7 @@ def export_image(): # --------------------------------------------------------- -# 11 - Endpoint "Crear archivos auxiliares": +# 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. @@ -621,21 +759,29 @@ def create_torrent_sum(): image_name = json_data.get("image") ou_subdir = json_data.get("ou_subdir") - # Evaluamos los parámetros obtenidos, para construir la llamada al script: + # Evaluamos los parámetros obtenidos, para construir la ruta de la imagen (relativa a "repo_path"): if ou_subdir == "none": - cmd = ['sudo', 'python3', f"{script_path}/createTorrentSum.py", image_name] + image_file_path = image_name else: - cmd = ['sudo', 'python3', f"{script_path}/createTorrentSum.py", f"{ou_subdir}/{image_name}"] + image_file_path = f"{ou_subdir}/{image_name}" + + # Construimos la llamada al script: + cmd = ['sudo', 'python3', f"{script_path}/createTorrentSum.py", image_file_path] try: - # Ejecutamos el script "createTorrentSum.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + # 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') # Evaluamos el resultado de la ejecución, y devolvemos una respuesta: - if result.returncode == 0: + if result.returncode is None: + # 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): + threading.Thread(target=check_aux_files, args=(f"{repo_path}{image_file_path}",)).start() + + # Informamos que los archivos auxiliares se están creando, y salimos del endpoint: return jsonify({ "success": True, - "output": "Files created successfully" + "output": "Creating auxiliar files..." }), 200 else: return jsonify({ @@ -663,7 +809,7 @@ def create_torrent_sum(): # --------------------------------------------------------- -# 12 - Endpoint "Enviar paquete Wake On Lan": +# 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. @@ -699,7 +845,7 @@ def send_wakeonlan(): # --------------------------------------------------------- -# 13 - Endpoint "Enviar una Imagen mediante UDPcast": +# 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. @@ -731,19 +877,28 @@ def send_udpcast(): }), 400 try: - # Ejecutamos el script "sendFileMcast.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + # 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 == 0: # NOTA: Devolverá "0" cuando finalize la transmisión, lo que finalizará también el proceso asociado, - return jsonify({ # pero si ningún cliente se conecta, el proceso quedará corriendo, y el script no devolverá nada. - "success": True, # Esto podría paliarse utilizando el parámetro "--autostart", pero creo que no será necesario. - "output": "Image sended successfully" + if result.returncode is None and process_running == True: + return jsonify({ + "success": True, + "output": "Sending image..." }), 200 else: return jsonify({ "success": False, - "error": result.stderr + "error": "Image send failed" + }), 500 + except subprocess.CalledProcessError as error: + return jsonify({ + "success": False, + "process exeption": str(error) }), 500 except Exception as error_description: return jsonify({ @@ -755,7 +910,7 @@ def send_udpcast(): # --------------------------------------------------------- -# 14 - Endpoint "Enviar una Imagen mediante UFTP": +# 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. @@ -785,19 +940,28 @@ def send_uftp(): }), 400 try: - # Ejecutamos el script "sendFileUFTP.py" (con los parámetros almacenados), y almacenamos el resultado: - result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='UTF8') + # 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 == 0: # NOTA: Devolverá "0" cuando finalize la transmisión, lo que finalizará también el proceso asociado, - return jsonify({ # pero si ningún cliente se conecta, el proceso finalizará automáticamente (y tambien devolverá "0""). + if result.returncode is None and process_running == True: + return jsonify({ "success": True, - "output": "Image sended successfully" + "output": "Sending image..." }), 200 else: return jsonify({ "success": False, - "error": result.stderr + "error": "Image send failed" + }), 500 + except subprocess.CalledProcessError as error: + return jsonify({ + "success": False, + "process exeption": str(error) }), 500 except Exception as error_description: return jsonify({ @@ -809,7 +973,7 @@ def send_uftp(): # --------------------------------------------------------- -# 15 - Endpoint "Enviar una Imagen mediante P2P": +# 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 en el que esté situada la imagen que recibe como parámetro. @@ -866,7 +1030,7 @@ def send_p2p(): # --------------------------------------------------------- -# 16 - Endpoint "Ver Estado de Transmisiones UDPcast": +# 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, @@ -909,7 +1073,7 @@ def get_udpcast_info(): # --------------------------------------------------------- -# 17 - Endpoint "Ver Estado de Transmisiones UFTP": +# 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, @@ -952,7 +1116,7 @@ def get_uftp_info(): # --------------------------------------------------------- -# 18 - Endpoint "Cancelar Transmisión UDPcast": +# 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. @@ -1014,7 +1178,7 @@ def stop_udpcast(imageId): # --------------------------------------------------------- -# 19 - Endpoint "Cancelar Transmisión UFTP": +# 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. @@ -1076,7 +1240,7 @@ def stop_uftp(imageId): # --------------------------------------------------------- -# 20 - Endpoint "Cancelar Transmisiones P2P": +# 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). diff --git a/api/swagger.yaml b/api/swagger.yaml index 8ac0a39..ddb3c2e 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1,6 +1,6 @@ swagger: "2.0" info: - title: "Ogrepository API" + title: "OgRepository API" version: "1.0" description: | API de ogRepository, programada en Flask. @@ -21,7 +21,7 @@ paths: tags: - "Estado de ogRepository" responses: - 200: + "200": description: "La información de estado se obtuvo exitosamente." schema: type: object @@ -95,7 +95,7 @@ paths: btlaunchmany: type: string example: "stopped" - 500: + "500": description: "Error al consultar y/o devolver la información de estado." schema: type: object @@ -118,7 +118,7 @@ paths: tags: - "Imágenes de Repositorio" responses: - 200: + "200": description: "La información de las imágenes se obtuvo exitosamente." schema: type: object @@ -266,7 +266,7 @@ paths: fullsum: type: string example: "22735b9070e4a8043371b8c6ae52b90d" - 500: + "500": description: "Error al consultar y/o devolver la información de las imágenes." schema: type: object @@ -287,7 +287,7 @@ paths: tags: - "Imágenes de Repositorio" responses: - 200: + "200": description: "La actualización de la información del repositorio se realizó exitosamente." schema: type: object @@ -298,7 +298,7 @@ paths: output: type: string example: "Repository info updated successfully" - 500: + "500": description: "Error al actualizar la información de las imágenes." schema: type: object @@ -326,7 +326,7 @@ paths: type: string description: "El ID de la imagen (corresponde al hash MD5 'fullsum' de la imagen)" responses: - 200: + "200": description: "La información de la imagen se obtuvo exitosamente." schema: type: object @@ -375,7 +375,7 @@ paths: fullsum: type: string example: "33575b9070e4a8043371b8c6ae52b80e" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -386,7 +386,7 @@ paths: error: type: string example: "Image not found" - 500: + "500": description: "Error al consultar y/o devolver la información de la imagen." schema: type: object @@ -418,7 +418,7 @@ paths: type: string description: "Método de eliminación (puede ser 'trash' para enviar a la papelera o 'permanent' para eliminar definitivamente)" responses: - 200: + "200": description: "La imagen se eliminó exitosamente." schema: type: object @@ -429,7 +429,7 @@ paths: output: type: string example: "Image deleted successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -440,7 +440,7 @@ paths: error: type: string example: "Image not found (inexistent or deleted)" - 500: + "500": description: > Error al eliminar la imagen. Puede ocurrir debido a: - Un error de ejecución del script (con salida no 0). @@ -454,7 +454,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la eliminación de la imagen." schema: type: object @@ -481,7 +481,7 @@ paths: type: string description: "El ID de la imagen (corresponde al hash MD5 'fullsum' de la imagen)" responses: - 200: + "200": description: > La imagen se ha chequeado exitosamente. El chequeo puede devolver dos resultados: - Si pasa la verificación de integridad. @@ -495,7 +495,7 @@ paths: output: type: string example: "Image file passed the Integrity Check correctly" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -506,7 +506,7 @@ paths: error: type: string example: "Image not found (inexistent or deleted)" - 500: + "500": description: > Error al chequear la imagen. Puede ocurrir debido a una excepción inesperada. schema: @@ -518,7 +518,7 @@ paths: exception: type: string example: "Generic error description for unexpected exceptions." - 200 (KO): + "200 (KO)": description: "La imagen se ha chequeado correctamente, pero no ha pasado el test de integridad." schema: type: object @@ -550,7 +550,7 @@ paths: type: string example: "image_id" responses: - 200: + "200": description: "La imagen se recuperó exitosamente." schema: type: object @@ -561,7 +561,7 @@ paths: output: type: string example: "Image recovered successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -572,7 +572,7 @@ paths: error: type: string example: "Image not found (inexistent or recovered previously)" - 500: + "500": description: > Error al recuperar la imagen. Puede ocurrir debido a: - Un error de ejecución del script (con salida no 0). @@ -586,7 +586,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la recuperación de la imagen." schema: type: object @@ -613,7 +613,7 @@ paths: type: string description: "El ID de la imagen (corresponde al hash MD5 'fullsum' de la imagen en la papelera)" responses: - 200: + "200": description: "La imagen se eliminó exitosamente." schema: type: object @@ -624,7 +624,7 @@ paths: output: type: string example: "Image deleted successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada en la papelera." schema: type: object @@ -635,7 +635,7 @@ paths: error: type: string example: "Image not found at trash" - 500: + "500": description: > Error al eliminar la imagen desde la papelera. Puede ocurrir debido a: - Un error de ejecución del script (con salida no 0). @@ -649,7 +649,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la eliminación de la imagen desde la papelera." schema: type: object @@ -694,7 +694,7 @@ paths: description: "Usuario para acceder al repositorio remoto" example: "user_name" responses: - 200: + "200": description: "La imagen se ha importado exitosamente." schema: type: object @@ -705,7 +705,7 @@ paths: output: type: string example: "Image imported successfully" - 400: + "400": description: "Error de conexión o imagen no disponible en el servidor remoto." schema: type: object @@ -716,7 +716,7 @@ paths: exception: type: string example: "Can't connect to remote server | Remote image not found | Remote image is locked" - 500: + "500": description: > Error interno al importar la imagen. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -728,7 +728,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la importación de la imagen." schema: type: object @@ -767,7 +767,7 @@ paths: description: "Usuario para acceder al repositorio remoto" example: "user_name" responses: - 200: + "200": description: "La imagen se ha exportado exitosamente." schema: type: object @@ -778,7 +778,7 @@ paths: output: type: string example: "Image exported successfully" - 400: + "400": description: "Error de conexión o imagen no disponible en el servidor remoto." schema: type: object @@ -789,7 +789,7 @@ paths: exception: type: string example: "Image is locked | Can't connect to remote server | Image already exists on remote server" - 500: + "500": description: > Error interno al exportar la imagen. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -801,7 +801,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la exportación de la imagen." schema: type: object @@ -836,7 +836,7 @@ paths: description: "Subdirectorio correspondiente a la OU (o 'none' si no es el caso)" example: "none" responses: - 200: + "200": description: "Los archivos se han creado exitosamente." schema: type: object @@ -847,7 +847,7 @@ paths: output: type: string example: "Files created successfully" - 400: + "400": description: "Error de conexión o imagen no disponible en el servidor remoto." schema: type: object @@ -858,7 +858,7 @@ paths: exception: type: string example: "Image not found | Image is locked" - 500: + "500": description: > Error interno al crear los archivos auxiliares. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -870,7 +870,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la creación de los archivos auxiliares." schema: type: object @@ -906,7 +906,7 @@ paths: description: "Dirección MAC del equipo a encender" example: "00:19:99:5c:bb:bb" responses: - 200: + "200": description: "El paquete Wake On Lan se ha enviado exitosamente." schema: type: object @@ -917,7 +917,7 @@ paths: output: type: string example: "Wake On Lan packet sent successfully" - 500: + "500": description: > Error interno al enviar el paquete Wake On Lan. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -929,7 +929,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante el envío del paquete Wake On Lan." schema: type: object @@ -950,7 +950,7 @@ paths: tags: - "Transferencia de Imágenes" responses: - 200: + "200": description: "La información de las transmisiones UDPcast activas se obtuvo exitosamente." schema: type: object @@ -969,7 +969,7 @@ paths: image_name: type: string example: "Ubuntu20.img" - 400: + "400": description: "No se han encontrado transmisiones UDPcast activas." schema: type: object @@ -980,7 +980,7 @@ paths: exception: type: string example: "No UDPCast active transmissions" - 500: + "500": description: > Error al comprobar las transmisiones UDPcast activas, posiblemente debido a un error inesperado durante la ejecución del script. schema: @@ -992,7 +992,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la comprobación de transmisiones UDPcast activas." schema: type: object @@ -1046,7 +1046,7 @@ paths: description: "Tiempo máximo de espera en segundos" example: 120 responses: - 200: + "200": description: "La imagen se ha enviado exitosamente mediante UDPcast." schema: type: object @@ -1057,7 +1057,7 @@ paths: output: type: string example: "Image sent successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -1068,7 +1068,7 @@ paths: error: type: string example: "Image not found" - 500: + "500": description: > Error interno al enviar la imagen mediante UDPcast. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -1080,7 +1080,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante el envío de la imagen mediante UDPcast." schema: type: object @@ -1101,7 +1101,7 @@ paths: tags: - "Transferencia de Imágenes" responses: - 200: + "200": description: "La información de las transmisiones UFTP activas se obtuvo exitosamente." schema: type: object @@ -1120,7 +1120,7 @@ paths: image_name: type: string example: "Ubuntu20.img" - 400: + "400": description: "No se han encontrado transmisiones UFTP activas." schema: type: object @@ -1131,7 +1131,7 @@ paths: exception: type: string example: "No UFTP active transmissions" - 500: + "500": description: > Error al comprobar las transmisiones UFTP activas, posiblemente debido a un error inesperado durante la ejecución del script. schema: @@ -1143,7 +1143,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante la comprobación de transmisiones UFTP activas." schema: type: object @@ -1186,7 +1186,7 @@ paths: description: "Velocidad de transmisión (con 'K' para Kbps, 'M' para Mbps, o 'G' para Gbps)" example: "1G" responses: - 200: + "200": description: "La imagen se ha enviado exitosamente mediante UFTP." schema: type: object @@ -1197,7 +1197,7 @@ paths: output: type: string example: "Image sent successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -1208,7 +1208,7 @@ paths: error: type: string example: "Image not found" - 500: + "500": description: > Error interno al enviar la imagen mediante UFTP. Puede ocurrir debido a un problema en la ejecución del script o una excepción inesperada. schema: @@ -1220,7 +1220,7 @@ paths: error: type: string example: "Script execution error description." - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante el envío de la imagen mediante UFTP." schema: type: object @@ -1253,7 +1253,7 @@ paths: type: string example: "image_id" responses: - 200: + "200": description: "La imagen se está enviando exitosamente a través de P2P." schema: type: object @@ -1264,7 +1264,7 @@ paths: output: type: string example: "Tracker and Seeder serving image correctly" - 400: + "400": description: "No se ha encontrado la imagen especificada." schema: type: object @@ -1275,7 +1275,7 @@ paths: error: type: string example: "Image not found" - 500: + "500": description: > Error al intentar iniciar el tracker o el seeder para el envío P2P. Puede ocurrir si alguno de los procesos no se inicia correctamente o si ocurre una excepción inesperada. @@ -1288,7 +1288,7 @@ paths: error: type: string example: "Tracker or Seeder (or both) not running" - 500 (Exception): + "500 (Exception)": description: "Error inesperado durante el proceso de envío P2P." schema: type: object @@ -1307,7 +1307,7 @@ paths: tags: - "Transferencia de Imágenes" responses: - 200: + "200": description: "Las transmisiones P2P se han cancelado exitosamente." schema: type: object @@ -1318,7 +1318,7 @@ paths: output: type: string example: "P2P transmissions canceled successfully" - 500 (Error del script): + "500 (Error del script)": description: "Error en la ejecución del script durante la cancelación de las transmisiones P2P." schema: type: object @@ -1329,7 +1329,7 @@ paths: error: type: string example: "Detailed error message from script stderr output." - 500 (Excepción general): + "500 (Excepción general)": description: "Excepción inesperada durante la cancelación de las transmisiones P2P." schema: type: object @@ -1357,7 +1357,7 @@ paths: description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum' asociado)" example: "image_id" responses: - 200: + "200": description: "La transmisión UDPcast se ha cancelado exitosamente." schema: type: object @@ -1368,7 +1368,7 @@ paths: output: type: string example: "Image transmission canceled successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada o no hay transmisiones UDPcast activas para la imagen especificada." schema: type: object @@ -1379,7 +1379,7 @@ paths: exception: type: string example: "No UDPCast active transmissions for specified image" - 500 (Error del script): + "500 (Error del script)": description: "Error en la ejecución del script durante la cancelación de la transmisión." schema: type: object @@ -1390,7 +1390,7 @@ paths: error: type: string example: "Detailed error message from script stderr output." - 500 (Error en verificación): + "500 (Error en verificación)": description: "Error inesperado al verificar las transmisiones UDPcast activas." schema: type: object @@ -1401,7 +1401,7 @@ paths: exception: type: string example: "Unexpected error checking UDPcast transmissions" - 500 (Error en finalización): + "500 (Error en finalización)": description: "Error inesperado al finalizar la transmisión UDPcast." schema: type: object @@ -1412,7 +1412,7 @@ paths: exception: type: string example: "Unexpected error finalizing UDPcast transmission" - 500 (Excepción general): + "500 (Excepción general)": description: "Excepción inesperada durante la cancelación de la transmisión UDPcast." schema: type: object @@ -1440,7 +1440,7 @@ paths: description: "Identificador de la imagen (correspondiente al contenido del archivo 'full.sum' asociado)" example: "image_id" responses: - 200: + "200": description: "La transmisión UFTP se ha cancelado exitosamente." schema: type: object @@ -1451,7 +1451,7 @@ paths: output: type: string example: "Image transmission canceled successfully" - 400: + "400": description: "No se ha encontrado la imagen especificada o no hay transmisiones UFTP activas para la imagen especificada." schema: type: object @@ -1462,7 +1462,7 @@ paths: exception: type: string example: "No UFTP active transmissions for specified image" - 500 (Error del script): + "500 (Error del script)": description: "Error en la ejecución del script durante la cancelación de la transmisión." schema: type: object @@ -1473,7 +1473,7 @@ paths: error: type: string example: "Detailed error message from script stderr output." - 500 (Error en verificación): + "500 (Error en verificación)": description: "Error inesperado al verificar las transmisiones UFTP activas." schema: type: object @@ -1484,7 +1484,7 @@ paths: exception: type: string example: "Unexpected error checking UFTP transmissions" - 500 (Error en finalización): + "500 (Error en finalización)": description: "Error inesperado al finalizar la transmisión UFTP." schema: type: object @@ -1495,7 +1495,7 @@ paths: exception: type: string example: "Unexpected error finalizing UFTP transmission" - 500 (Excepción general): + "500 (Excepción general)": description: "Excepción inesperada durante la cancelación de la transmisión UFTP." schema: type: object @@ -1507,4 +1507,3 @@ paths: type: string example: "General error description for unexpected exceptions" -