diff --git a/README.md b/README.md index 18944e8..725b867 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ El presente documento detalla los endpoints de la API, con sus respectivos pará 4. [Eliminar una Imagen](#eliminar-una-imagen) - `DELETE /ogrepository/v1/images/delete-image` 5. [Recuperar una Imagen](#recuperar-una-imagen) - `POST /ogrepository/v1/images/recover-image` 6. [Importar una Imagen](#importar-una-imagen) - `POST /ogrepository/v1/images/import-image` -7. [Enviar una Imagen mediante UDPcast](#enviar-una-imagen-mediante-udpcast) - `POST /ogrepository/v1/images/send-udpcast` -8. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/images/send-uftp` -9. [Crear archivo ".torrent"](#crear-archivo-torrent) - `POST /ogrepository/v1/images/create-torrent` +7. [Crear archivos "sum" y "torrent"](#crear-archivos-sum-y-torrent) - `POST /ogrepository/v1/images/create-torrentsum` +8. [Enviar una Imagen mediante UDPcast](#enviar-una-imagen-mediante-udpcast) - `POST /ogrepository/v1/images/send-udpcast` +9. [Enviar una Imagen mediante UFTP](#enviar-una-imagen-mediante-uftp) - `POST /ogrepository/v1/images/send-uftp` 10. [Enviar una Imagen mediante P2P](#enviar-una-imagen-mediante-p2p) - `POST /ogrepository/v1/images/send-p2p` 11. [Chequear integridad de Imagen](#chequear-integridad-de-imagen) - `GET /ogrepository/v1/images/check-image` 12. [Ver Estado de Transmisiones Multicast-P2P](#ver-estado-de-transmisiones-multicast-p2p) - @@ -58,7 +58,6 @@ curl -X GET -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 200 OK:** La información de las imágenes se obtuvo exitosamente. - **Contenido:** Información de imágenes en formato JSON. ```json - { "REPOSITORY": { "directory": "/opt/opengnsys/images", @@ -154,7 +153,6 @@ curl -X GET -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 200 OK:** La información de la imagen se obtuvo exitosamente. - **Contenido:** Información de la imagen en formato JSON. ```json - { "directory": "/opt/opengnsys/images", "images": [ @@ -269,6 +267,29 @@ curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 400 Bad Request:** No se ha encontrado la imagen y/o el equipo remoto especificados. - **Código 200 OK:** La imagen se ha importado exitosamente. +--- +### Crear archivos "sum" y "torrent" + +Se crearán los archivos ".sum", ".full.sum" y ".torrent", para la imagen especificada como parámetro. +Se puede hacer con el script "**createTorrentSum.py**", que hemos programado recientemente. + +**URL:** `/ogrepository/v1/images/create-torrentsum` +**Método HTTP:** POST + +**Cuerpo de la Solicitud (JSON):** +- **image**: Nombre de la imagen (con extensión). +- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). + +**Ejemplo de Solicitud:** + +```bash +curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/create-torrentsum +``` +**Respuestas:** +- **Código 500 Internal Server Error:** Ocurrió un error al crear los archivos. +- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. +- **Código 200 OK:** Los archivos se han creado exitosamente. + --- ### Enviar una Imagen mediante UDPcast @@ -325,30 +346,6 @@ curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d - **Código 400 Bad Request:** No se ha encontrado la imagen especificada. - **Código 200 OK:** La imagen se ha enviado exitosamente. ---- -### Crear archivo .torrent - -Se creará un archivo ".torrent" para la imagen especificada como parámetro. -Se debe crear un script que realice dicha tarea, porque actualmente se hace mediante el script "**torrent-creator**", que se ejecuta por crontab a cada minuto (y crea un archivo ".torrent" por cada imagen que no tenga uno asociado). -**NOTA**: Puede que sea preferible que esta acción la realice el propio ogLive al crear una imagen, ya que también tiene las herramientas para hacerlo. - -**URL:** `/ogrepository/v1/images/create-torrent` -**Método HTTP:** POST - -**Cuerpo de la Solicitud (JSON):** -- **image**: Nombre de la imagen (con extensión). -- **ou_subdir**: Subdirectorio correspondiente a la OU (o "none" si no es el caso). - -**Ejemplo de Solicitud:** - -```bash -curl -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" -d '{"image":"Windows10.img", "ou_subdir":"none"}' http://example.com/ogrepository/v1/images/create-torrent -``` -**Respuestas:** -- **Código 500 Internal Server Error:** Ocurrió un error al crear el archivo torrent. -- **Código 400 Bad Request:** No se ha encontrado la imagen especificada. -- **Código 200 OK:** El archivo torrent se ha creado exitosamente. - --- ### Enviar una Imagen mediante P2P diff --git a/py_scripts/createTorrentSum.py b/py_scripts/createTorrentSum.py new file mode 100644 index 0000000..af8c1b4 --- /dev/null +++ b/py_scripts/createTorrentSum.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Este script crea los archivos ".sum", ".full.sum" y ".torrent" para la imagen que recibe como parámetro. +En principio, debería hacer lo mismo que el script bash original (cuyo nombre es "torrent-creator"). + +Debería ser llamado por ogCore u ogLive cada vez que se cree una imagen. + + Parámetros +------------ +sys.argv[1] - Nombre completo de la imagen a eliminar (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 +---------- +./createTorrentSum.py [ou_subdir/]image_name|/image_path/image_name + + Ejemplos + --------- +./createTorrentSum.py image1.img +./createTorrentSum.py /opt/opengnsys/images/image1.img +./createTorrentSum.py ou_subdir/image1.img +./createTorrentSum.py /ou_subdir/image1.img +./createTorrentSum.py /opt/opengnsys/images/ou_subdir/image1.img +""" + +# -------------------------------------------------------------------------------------------- +# IMPORTS +# -------------------------------------------------------------------------------------------- + +import os +import sys +import subprocess +import hashlib + + +# -------------------------------------------------------------------------------------------- +# VARIABLES +# -------------------------------------------------------------------------------------------- + +script_name = os.path.basename(__file__) +repo_path = '/opt/opengnsys/images/' +config_file = '/opt/opengnsys/etc/ogAdmRepo.cfg' + + +# -------------------------------------------------------------------------------------------- +# 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 al archivo a eliminar + (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_md5_sum(file_path, megabytes=1): + """ Calcula y retorna el hash MD5 del último MB del archivo de imagen que recibe como parámetro. + Se utiliza para crear el archivo ".sum" (para transferencias Unicast y Multicast). + """ + hash_md5 = hashlib.md5() + with open(file_path, "rb") as f: + f.seek(-megabytes * 1024 * 1024, os.SEEK_END) + data = f.read(megabytes * 1024 * 1024) + hash_md5.update(data) + return hash_md5.hexdigest() + + +def get_md5_fullsum(file_path): + """ Calcula y retorna el hash MD5 del archivo de imagen que recibe como parámetro. + Se utiliza para crear el archivo ".full.sum" (para transferencias P2P). + """ + hash_md5 = hashlib.md5() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def get_IPlocal(): + """ Retorna la IP asociada a la variable "IPlocal", del archivo '/opt/opengnsys/etc/ogAdmRepo.cfg' + (que corresponde a la IP del repositorio). + """ + with open(config_file, 'r') as file: + for line in file: + if line.startswith('IPlocal'): + IPlocal = line.split('=')[1].strip() + return IPlocal + + +def create_torrent(file_path, torrent_file, datafullsum): + """ Crea un archivo ".torrent" para la imagen que recibe como primer parámetro. + Obtiene la IP del repositorio llamando a la función "get_IPlocal", + que a su vez la obtiene del archivo '/opt/opengnsys/etc/ogAdmRepo.cfg'. + """ + # Almacenamos la IP del repositorio, y construimos la URL del tracker: + repo_ip = get_IPlocal() + tracker_url = f"http://{repo_ip}:6969/announce" + + # Creamos una lista con el comando para crear el torrrent, y lo imprimimos con espacios: + splitted_cmd = f"nice -n 0 ctorrent -t {file_path} -u {tracker_url} -s {torrent_file} -c {datafullsum} -l 4194304".split() + print(f"Sending command: {' '.join(splitted_cmd)}") + + # Ejecutamos el comando en el sistema, e imprimimos el resultado: + try: + result = subprocess.run(splitted_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print(f"ReturnCode: {result.returncode}") + except subprocess.CalledProcessError as error: + print(f"ReturnCode: {error.returncode}") + print(f"Error Output: {error.stderr.decode()}") + except Exception as error: + print(f"Se ha producido un error inesperado: {error}") + + + +# -------------------------------------------------------------------------------------------- +# 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 de la 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) + + # Si la imagen está bloqueada, imprimimos un mensaje de error y salimos del script: + if os.path.exists(f"{file_path}.lock"): + print("Image is locked") + sys.exit(3) + + # Creamos un archivo de bloqueo: + open(f"{file_path}.lock", "w").close() + + # Construimos las rutas completas de los archivos ".sum", ".full.sum" y ".torrent": + sum_file = f"{file_path}.sum" + fullsum_file = f"{file_path}.full.sum" + torrent_file = f"{file_path}.torrent" + + # Creamos el archivo ".sum" (para transferencias Unicast y Multicast): + print("Creating '.sum' file...") + with open(sum_file, 'w') as file: + datasum = get_md5_sum(file_path) + file.write(datasum) + + # Creamos el archivo ".full.sum" (para transferencias P2P): + print("Creating '.ful.sum' file...") + with open(fullsum_file, 'w') as file: + datafullsum = get_md5_fullsum(file_path) + file.write(datafullsum) + + # Creamos el archivo ".torrent" (siempre que no exista): + if not os.path.exists(torrent_file): + create_torrent(file_path, torrent_file, datafullsum) + else: + print("Torrent file exists") + + # Eliminamos el archivo de bloqueo: + os.remove(f"{file_path}.lock") + + + +# -------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + main() + +# --------------------------------------------------------------------------------------------