From 126aa3cf8f55f3b956b9409493d0da841ad581a0 Mon Sep 17 00:00:00 2001 From: ggil Date: Mon, 21 Oct 2024 17:27:46 +0200 Subject: [PATCH] refs #631 - Add 'getUFTPInfo.py', 'stopUFTP.py' and associated endpoints --- README.md | 62 ++++++++++++++-- api/README.md | 62 ++++++++++++++-- api/repo_api.py | 114 +++++++++++++++++++++++++++-- bin/getUFTPInfo.py | 99 +++++++++++++++++++++++++ bin/sendFileUFTP.py | 17 +++-- bin/stopUFTP.py | 172 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 509 insertions(+), 17 deletions(-) create mode 100644 bin/getUFTPInfo.py create mode 100644 bin/stopUFTP.py diff --git a/README.md b/README.md index 7688f4a..2bc905c 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,15 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 14. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/uftp` 15. [Enviar una Imagen mediante P2P](#enviar-una-imagen-mediante-p2p) - `POST /ogrepository/v1/p2p` 16. [Ver Estado de Transmisiones UDPcast](#ver-estado-de-transmisiones-udpcast) - `GET /ogrepository/v1/udpcast` -17. [Cancelar Transmisión UDPcast](#cancelar-transmisión-udpcast) - `DELETE /ogrepository/v1/udpcast/images/{ID_img}` -18. [Cancelar Transmisiones P2P](#cancelar-transmisiones-p2p) - `DELETE /ogrepository/v1/p2p` +17. [Ver Estado de Transmisiones UFTP](#ver-estado-de-transmisiones-uftp) - `GET /ogrepository/v1/uftp` +18. [Cancelar Transmisión UDPcast](#cancelar-transmisión-udpcast) - `DELETE /ogrepository/v1/udpcast/images/{ID_img}` +19. [Cancelar Transmisión UFTP](#cancelar-transmisión-uftp) - `DELETE /ogrepository/v1/uftp/images/{ID_img}` +20. [Cancelar Transmisiones P2P](#cancelar-transmisiones-p2p) - `DELETE /ogrepository/v1/p2p` --- ### Obtener Información de Estado de ogRepository -Se devolverá informacion de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato JSON. +Se devolverá informacion de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato JSON. Se puede utilizar el script "**getRepoStatus.py**, que debe ser llamado por el endpoint. **NOTA**: En los apartados "services" y "processes" he especificado los servicios y procesos que me han parecido interesantes, pero se puede añadir o eliminar los que se desee. @@ -562,6 +564,37 @@ curl -X GET -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/udpc } ``` --- +### Ver Estado de Transmisiones UFTP + +Se devolverá el pid de los procesos de transferencias UFTP activas, y sus imágenes asociadas (con nombre e ID), en formato JSON, o un mensaje informativo si no hay procesos activos, o si se produce un error. +Se puede hacer con el script "**getUFTPInfo.py**", que debe ser llamado por el endpoint. + +**URL:** `/ogrepository/v1/uftp` +**Método HTTP:** GET + +**Ejemplo de Solicitud:** + +```bash +curl -X GET -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/uftp +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al comprobar las transmisiones UFTP. +- **Código 400 Bad Request:** No se han encontrado transmisiones UFTP activas. +- **Código 200 OK:** La información de las transmisiones UFTP activas se obtuvo exitosamente. + - **Contenido:** Información de las transmisiones UFTP activas en formato JSON. + ```json + { + "3427": { + "image_id": "22735b9070e4a8043371b8c6ae52b90d", + "image_name": "Ubuntu20.img" + }, + "4966": { + "image_id": "9e7cd32c606ebe5bd39ba212ce7aeb02", + "image_name": "Windows10.img" + } + } + ``` +--- ### Cancelar Transmisión UDPcast Se cancelará la transmisión por UDPcast activa de la imagen especificada como parámetro, deteniendo el proceso "udp-sender" asociado a dicha imagen. @@ -579,9 +612,30 @@ curl -X DELETE -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/u **Respuestas:** - **Código 500 Internal Server Error:** Ocurrió un error al cancelar la transmisión UDPcast. - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. -- **Código 400 Bad Request:** No hay transmisiones UCPcast activas para la imagen especificada. +- **Código 400 Bad Request:** No hay transmisiones UDPcast activas para la imagen especificada. - **Código 200 OK:** La transmisión UDPcast se ha cancelado exitosamente. +--- +### Cancelar Transmisión UFTP + +Se cancelará la transmisión por UFTP activa de la imagen especificada como parámetro, deteniendo el proceso "uftp" asociado a dicha imagen. +Se puede hacer con el script "**stopUFTP.py**", que debe ser llamado por el endpoint. +**NOTA**: La versión actual de este script requiere que se le pase el nombre de la imagen (con extensión, e incluyendo el nombre del directorio correspondiente a la OU, si fuera el caso) como único parámetro. Este dato se obtiene en la API, a partir del ID de la imagen (que corresponde al contenido del archivo "full.sum"). + +**URL:** `/ogrepository/v1/uftp/images/{ID_img}` +**Método HTTP:** DELETE + +**Ejemplo de Solicitud:** + +```bash +curl -X DELETE -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/uftp/images/{ID_img} +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al cancelar la transmisión UFTP. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 400 Bad Request:** No hay transmisiones UFTP activas para la imagen especificada. +- **Código 200 OK:** La transmisión UFTP se ha cancelado exitosamente. + --- ### Cancelar Transmisiones P2P diff --git a/api/README.md b/api/README.md index 2f6e0ef..5d6deaa 100644 --- a/api/README.md +++ b/api/README.md @@ -24,13 +24,15 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 14. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/uftp` 15. [Enviar una Imagen mediante P2P](#enviar-una-imagen-mediante-p2p) - `POST /ogrepository/v1/p2p` 16. [Ver Estado de Transmisiones UDPcast](#ver-estado-de-transmisiones-udpcast) - `GET /ogrepository/v1/udpcast` -17. [Cancelar Transmisión UDPcast](#cancelar-transmisión-udpcast) - `DELETE /ogrepository/v1/udpcast/images/{ID_img}` -18. [Cancelar Transmisiones P2P](#cancelar-transmisiones-p2p) - `DELETE /ogrepository/v1/p2p` +17. [Ver Estado de Transmisiones UFTP](#ver-estado-de-transmisiones-uftp) - `GET /ogrepository/v1/uftp` +18. [Cancelar Transmisión UDPcast](#cancelar-transmisión-udpcast) - `DELETE /ogrepository/v1/udpcast/images/{ID_img}` +19. [Cancelar Transmisión UFTP](#cancelar-transmisión-uftp) - `DELETE /ogrepository/v1/uftp/images/{ID_img}` +20. [Cancelar Transmisiones P2P](#cancelar-transmisiones-p2p) - `DELETE /ogrepository/v1/p2p` --- ### Obtener Información de Estado de ogRepository -Se devolverá informacion de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato JSON. +Se devolverá informacion de CPU, memoria RAM, disco duro y el estado de ciertos servicios y procesos de ogRepository, en formato JSON. Se puede utilizar el script "**getRepoStatus.py**, que debe ser llamado por el endpoint. **NOTA**: En los apartados "services" y "processes" he especificado los servicios y procesos que me han parecido interesantes, pero se puede añadir o eliminar los que se desee. @@ -549,6 +551,37 @@ curl -X GET -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/udpc } ``` --- +### Ver Estado de Transmisiones UFTP + +Se devolverá el pid de los procesos de transferencias UFTP activas, y sus imágenes asociadas (con nombre e ID), en formato JSON, o un mensaje informativo si no hay procesos activos, o si se produce un error. +Se puede hacer con el script "**getUFTPInfo.py**", que debe ser llamado por el endpoint. + +**URL:** `/ogrepository/v1/uftp` +**Método HTTP:** GET + +**Ejemplo de Solicitud:** + +```bash +curl -X GET -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/uftp +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al comprobar las transmisiones UFTP. +- **Código 400 Bad Request:** No se han encontrado transmisiones UFTP activas. +- **Código 200 OK:** La información de las transmisiones UFTP activas se obtuvo exitosamente. + - **Contenido:** Información de las transmisiones UFTP activas en formato JSON. + ```json + { + "3427": { + "image_id": "22735b9070e4a8043371b8c6ae52b90d", + "image_name": "Ubuntu20.img" + }, + "4966": { + "image_id": "9e7cd32c606ebe5bd39ba212ce7aeb02", + "image_name": "Windows10.img" + } + } + ``` +--- ### Cancelar Transmisión UDPcast Se cancelará la transmisión por UDPcast activa de la imagen especificada como parámetro, deteniendo el proceso "udp-sender" asociado a dicha imagen. @@ -566,9 +599,30 @@ curl -X DELETE -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/u **Respuestas:** - **Código 500 Internal Server Error:** Ocurrió un error al cancelar la transmisión UDPcast. - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. -- **Código 400 Bad Request:** No hay transmisiones UCPcast activas para la imagen especificada. +- **Código 400 Bad Request:** No hay transmisiones UDPcast activas para la imagen especificada. - **Código 200 OK:** La transmisión UDPcast se ha cancelado exitosamente. +--- +### Cancelar Transmisión UFTP + +Se cancelará la transmisión por UFTP activa de la imagen especificada como parámetro, deteniendo el proceso "uftp" asociado a dicha imagen. +Se puede hacer con el script "**stopUFTP.py**", que debe ser llamado por el endpoint. +**NOTA**: La versión actual de este script requiere que se le pase el nombre de la imagen (con extensión, e incluyendo el nombre del directorio correspondiente a la OU, si fuera el caso) como único parámetro. Este dato se obtiene en la API, a partir del ID de la imagen (que corresponde al contenido del archivo "full.sum"). + +**URL:** `/ogrepository/v1/uftp/images/{ID_img}` +**Método HTTP:** DELETE + +**Ejemplo de Solicitud:** + +```bash +curl -X DELETE -H "Authorization: $API_KEY" http://example.com/ogrepository/v1/uftp/images/{ID_img} +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al cancelar la transmisión UFTP. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 400 Bad Request:** No hay transmisiones UFTP activas para la imagen especificada. +- **Código 200 OK:** La transmisión UFTP se ha cancelado exitosamente. + --- ### Cancelar Transmisiones P2P diff --git a/api/repo_api.py b/api/repo_api.py index 4757709..cb21de6 100644 --- a/api/repo_api.py +++ b/api/repo_api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ - API de ogRepository, programada en Flask. + 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 ogRepo. En ciertos casos, transforma los parámetros recibidos desde el portal, para adaptarlos a los que es necesario enviar a los scripts @@ -31,6 +31,7 @@ repo_file = '/opt/opengnsys/etc/repoinfo.json' trash_file = '/opt/opengnsys/etc/trashinfo.json' + # -------------------------------------------------------------------------------------------- # FUNCTIONS # -------------------------------------------------------------------------------------------- @@ -906,7 +907,50 @@ def get_udpcast_info(): # --------------------------------------------------------- -# 17 - Endpoint "Cancelar Transmisión UDPcast": +# 17 - Endpoint "Ver Estado de Transmisiones UFTP": +@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. + """ + try: + # Ejecutamos el script "getUFTPInfo.py", y almacenamos el resultado: + result = subprocess.run(['sudo', '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: + return jsonify({ + "success": True, + "output": json.loads(result.stdout) + }), 200 + else: + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 1" in str(error_description): + return jsonify({ + "success": False, + "exception": "No UFTP active transmissions" + }), 400 + elif "exit status 2" in str(error_description): + return jsonify({ + "success": False, + "exception": "Unexpected error checking UFTP transmissions" + }), 500 + else: + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 18 - Endpoint "Cancelar Transmisión UDPcast": @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. @@ -963,12 +1007,74 @@ def stop_udpcast(imageId): "success": False, "exception": str(error_description) }), 500 - + # --------------------------------------------------------- -# 18 - Endpoint "Cancelar Transmisiones P2P": +# 19 - Endpoint "Cancelar Transmisión UFTP": +@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 (con subdirectorio de OU, si procede). + """ + # Obtenemos el nombre y la extensión de la imagen (y el subdirectorio de OU, si fuera el caso): + param_dict = get_image_params(imageId, "repo") + + # Evaluamos los parámetros obtenidos, para construir la llamada al script, 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}/stopUFTP.py", f"{param_dict['subdir']}/{param_dict['name']}.{param_dict['extension']}"] + else: + cmd = ['sudo', 'python3', f"{script_path}/stopUFTP.py", f"{param_dict['name']}.{param_dict['extension']}"] + else: + return jsonify({ + "success": False, + "error": "Image not found" + }), 400 + + try: + # 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: + return jsonify({ + "success": True, + "output": "Image transmission canceled successfully" + }), 200 + else: + return jsonify({ + "success": False, + "error": result.stderr + }), 500 + except Exception as error_description: + if "exit status 3" in str(error_description): + return jsonify({ + "success": False, + "exception": "No UFTP active transmissions for specified image" + }), 400 + elif "exit status 4" in str(error_description): + return jsonify({ + "success": False, + "exception": "Unexpected error checking UFTP transmissions" + }), 500 + elif "exit status 5" in str(error_description): + return jsonify({ + "success": False, + "exception": "Unexpected error finalizing UFTP transmission" + }), 500 + else: + return jsonify({ + "success": False, + "exception": str(error_description) + }), 500 + + +# --------------------------------------------------------- + + +# 20 - Endpoint "Cancelar Transmisiones P2P": @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/bin/getUFTPInfo.py b/bin/getUFTPInfo.py new file mode 100644 index 0000000..a34ee21 --- /dev/null +++ b/bin/getUFTPInfo.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +""" +Este script busca procesos activos de "uftp", y si encuentra alguno devuelve el pid y la imagen asociada de cada uno de ellos, en una estructura JSON. +Si no encuentra ninguno, o si se produce un error, imprime un mensaje informativo. +No recibe ningún parámetro. + +En la práctica, permite comprobar las transminisones UFTP activas, porque cuando finalizan también finaliza el proceso asociado. +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import subprocess +import json +import sys + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +repo_path = '/opt/opengnsys/images/' + + +# -------------------------------------------------------------------------------------------- +# FUNCTIONS +# -------------------------------------------------------------------------------------------- + + +def get_uftp_processes(): + """ Busca procesos de "uftp", y si los encuentra retorna el pid, el ID, y la imagen asociada de cada uno de ellos, en un diccionario. + Si no encuentra ningun proceso, o si se produce un error, retorna un mensaje. + """ + try: + # Obtenemos todos los procesos, 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 "uftp": + process_list = [line for line in out.split('\n') if 'uftp' in line] + # Si hemos encontrado procesos de udp-sender creamos un diccionario para almacenarlos: + if process_list != []: + result_dict = {} + # Iteramos los procesos y extraemos el pid, el nombre de la imagen (con subdirectorio de OU, si es el caso), y la ruta de la imagen de cada uno: + for process in process_list: + pid = process.split()[1] + image_name = process.split(repo_path)[1] + image_path = process.split()[-1] + # Obtenemos el ID de la imagen actual: + with open(f"{image_path}.full.sum", 'r') as file: + image_id = file.read().strip('\n') + # Creamos una clave en el diccionario de resultados, correspondiente a la imagen actual: + result_dict[pid] = {'image_id':image_id, 'image_name':image_name} + # Retornamos el diccionario de resultados: + return result_dict + # Si no hemos encontrado procesos de uftp retornamos un mensaje: + else: + return "uftp process not found" + # Si se ha producido una excepción, retornamos un mensaje: + except Exception: + return "Unexpected error" + + + +# -------------------------------------------------------------------------------------------- +# MAIN +# -------------------------------------------------------------------------------------------- + + +def main(): + """ + """ + # Obtenemos información sobre los procesos "uftp": + results = get_uftp_processes() + + # Si no hay procesos activos, o si se ha producido un error, imprimimos un mensaje explicativo, y salimos del script: + if results == "uftp process not found": + print("No UFTP active transmissions") + sys.exit(1) + elif results == "Unexpected error": + print("Unexpected error checking UFTP transmissions") + sys.exit(2) + # Si hay procesos activos, convertimos el diccionario de resultados a JSON, e imprimimos este: + else: + json_data = json.dumps(results, indent=4) + print(json_data) + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# -------------------------------------------------------------------------------------------- diff --git a/bin/sendFileUFTP.py b/bin/sendFileUFTP.py index c765b31..dabd726 100644 --- a/bin/sendFileUFTP.py +++ b/bin/sendFileUFTP.py @@ -4,7 +4,8 @@ """ Este script envía mediante UFTP la imagen recibida como primer parámetro, al puerto e IP (Multicast o Unicast) especificados en el segundo parámetro, a la velocidad de transferencia tambíén especificada en el segundo parámetro (la sintaxis de este parámetro es "Port:IP:Bitrate"). -Previamente, los clientes deben haberse puesto a escuchar en la IP Multicast correspondiente (tanto para envíos Multicast como para envíos Unicast). +Previamente, los clientes deben haberse puesto a escuchar con un proceso "uftpd" en la IP Multicast correspondiente (tanto para envíos Multicast como para envíos Unicast). +NOTA: La imagen se envía a la ruta de la caché de los clientes (que actualmente es "/opt/opengnsys/cache"). Paquetes APT requeridos: "uftp" (se puede instalar con "sudo apt install uftp"). @@ -49,6 +50,7 @@ import subprocess script_name = os.path.basename(__file__) repo_path = '/opt/opengnsys/images/' +cache_path = '/opt/opengnsys/cache' log_file = '/opt/opengnsys/log/uftp.log' @@ -137,11 +139,16 @@ def main(): # Obtenemos la ruta completa al archivo a enviar: file_path = build_file_path() - # Si el fichero no es accesible, devolvermos un error, y salimos del script: - if not os.path.isfile(file_path): - print(f"{script_name} Error: Fichero \"{file_path}\" no accesible") + # Si no existe el archivo de imagen, imprimimos un mensaje de error y salimos del script: + if not os.path.exists(file_path): + print("Image file doesn't exist") sys.exit(2) + # Si no existe el archivo de log, lo creamos: + if not os.path.exists(log_file): + print("Creating log file...") + open(log_file, "w").close() + # Almacenamos los elementos del segundo parámetro en variables (su formato es "puerto:ip:bitrate"): param_list = sys.argv[2].split(':') port, ip, bitrate = param_list @@ -150,7 +157,7 @@ def main(): bitrate = calculate_bitrate(bitrate) # Creamos una lista con el comando a enviar (esto es requerido por la función "subprocess.run"), e imprimimos el comando con espacios: - splitted_cmd = f"uftp -M {ip} -p {port} -L {log_file} -Y aes256-cbc -h sha256 -e rsa -c -K 1024 -R {bitrate} {file_path}".split() + splitted_cmd = f"uftp -M {ip} -p {port} -L {log_file} -o -D {cache_path} -Y aes256-cbc -h sha256 -e rsa -c -K 1024 -R {bitrate} {file_path}".split() print(f"Sending command: {' '.join(splitted_cmd)}") diff --git a/bin/stopUFTP.py b/bin/stopUFTP.py new file mode 100644 index 0000000..377f652 --- /dev/null +++ b/bin/stopUFTP.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +""" +Este script finaliza el proceso "uftp" asociado a la imagen que recibe como parámetro, + lo que en la práctica hará que se cancele la transmisión existente de dicha imagen mediante UFTP. + + Parámetros +------------ +sys.argv[1] - Nombre completo de la imagen a cancelar su transmisión (con o sin ruta), pero incluyendo el subdirectorio correspondiente a la OU, si es el caso. + - Ejemplo1: image1.img + - Ejemplo2: /opt/opengnsys/images/image1.img + - Ejemplo3: ou_subdir/image1.img + - Ejemplo4: /ou_subdir/image1.img + - Ejemplo5: /opt/opengnsys/images/ou_subdir/image1.img + + Sintaxis +---------- +./stopUDPcast.py [ou_subdir/]image_name|/image_path/image_name + + Ejemplos + --------- +./stopUDPcast.py image1.img +./stopUDPcast.py /opt/opengnsys/images/image1.img +./stopUDPcast.py ou_subdir/image1.img +./stopUDPcast.py /ou_subdir/image1.img +./stopUDPcast.py /opt/opengnsys/images/ou_subdir/image1.img +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import os +import sys +import subprocess + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +script_name = os.path.basename(__file__) +repo_path = '/opt/opengnsys/images/' + + +# -------------------------------------------------------------------------------------------- +# FUNCTIONS +# -------------------------------------------------------------------------------------------- + + +def show_help(): + """ Imprime la ayuda, cuando se ejecuta el script con el parámetro "help". + """ + help_text = f""" + Sintaxis: {script_name} [ou_subdir/]image_name|/image_path/image_name + Ejemplo1: {script_name} image1.img + Ejemplo2: {script_name} /opt/opengnsys/images/image1.img + Ejemplo3: {script_name} ou_subdir/image1.img + Ejemplo4: {script_name} /ou_subdir/image1.img + Ejemplo5: {script_name} /opt/opengnsys/images/ou_subdir/image1.img + """ + print(help_text) + + +def check_params(): + """ Comprueba que se haya enviado la cantidad correcta de parámetros, y en el formato correcto. + Si no es así, muestra un mensaje de error, y sale del script. + LLama a la función "show_help" cuando se ejecuta el script con el parámetro "help". + """ + # Si se ejecuta el script con el parámetro "help", se muestra la ayuda, y se sale del script: + if len(sys.argv) == 2 and sys.argv[1] == "help": + show_help() + sys.exit(0) + # Si se ejecuta el script con más o menos de 1 parámetro, se muestra un error y la ayuda, y se sale del script: + elif len(sys.argv) != 2: + print(f"{script_name} Error: Formato incorrecto: Se debe especificar 1 parámetro") + show_help() + sys.exit(1) + + +def build_file_path(): + """ Construye la ruta completa del archivo a chequear + (agregando "/opt/opengnsys/images/" si no se ha especificado en el parámetro). + """ + param_path = sys.argv[1] + # Si la ruta comienza con una barra, pero que no corresponde a "repo_path" + # (porque corresponderá al subdirectorio de una OU), eliminamos la barra: + if param_path.startswith('/') and not param_path.startswith(repo_path): + param_path = param_path.lstrip('/') + # Construimos la ruta completa: + if not param_path.startswith(repo_path): + file_path = os.path.join(repo_path, param_path) + else: + file_path = param_path + return file_path + + +def get_process_pid(file_path): + """ Busca un proceso que contenga "uftp" y la ruta de la imagen que recibe como parámetro, + y si lo encuentra almacena y retorna su pid asociado. + Si no encuentra ningún proceso que cumpla las condiciones (o si se produce una excepción) sale del script. + """ + try: + # Obtenemos todos los procesos, 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 "uftp" y la imagen especificada como parámetro: + filtered_lines = [line for line in out.split('\n') if 'uftp' in line and file_path in line] + # Si hemos encontrado un proceso retornamos su pid, y si no imprimimos un mensaje de error y salimos del script: + if filtered_lines != []: + pid = filtered_lines[0].split()[1] + return pid + else: + print("uftp process not found") + sys.exit(3) + # Si se ha producido una excepción, imprimimos el error y salimos del script: + except Exception as error_description: + print(f"Unexpected error: {error_description}") + sys.exit(4) + + +def kill_uftp(pid): + """ Finaliza el proceso asociado al pid que recibe como parámetro, e imprime el return code. + Si se produce una excepción, imprime el error y sale del script. + """ + try: + # Finalizamos el proceso asociado al pid especificado como parámetro, e imprimimos el return code: + result = subprocess.run(f"kill {pid}".split(), check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print(f"ReturnCode: {result.returncode}") + # Si se ha producido una excepción, imprimimos el error y salimos del script: + except Exception as error_description: + print(f"Unexpected error: {error_description}") + sys.exit(5) + + + +# -------------------------------------------------------------------------------------------- +# MAIN +# -------------------------------------------------------------------------------------------- + + +def main(): + """ + """ + # Evaluamos si se ha enviado la cantidad correcta de parámetros, y en el formato correcto: + check_params() + + # Obtenemos la ruta completa al archivo de imagen: + file_path = build_file_path() + + # Si no existe el archivo de imagen, imprimimos un mensaje de error y salimos del script: + if not os.path.exists(file_path): + print("Image file doesn't exist") + sys.exit(2) + + # Obtenemos el pid del proceso "uftp" asociado a la imagen especificada: + pid = get_process_pid(file_path) + + # Finalizamos el proceso "uftp" encontrado: + kill_uftp(pid) + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# --------------------------------------------------------------------------------------------