#!/bin/bash # google_cloud_storage ################################################################################# # Script que sincroniza los los productos necesarios por meteored # (se encuentran en /home/imagenes.meteored.com) con el Storage de Google Cloud. # # Funcionamiento: # - Cada directorio con productos de meteored en /home/imagenes.meteored.com # tiene asociado un archivo de finalización tipo "finished_*" en # el directorio /home/gfs/meteored. # - Comprobamos uno por uno que existen los archivos de finalización y en caso # de que existan sincronizamos su directorio asociado de productos con # el Storage de Google Cloud. # # Esta tarea se ejecutará en los siguientes servidores: # tiempo.ogimet.com -> servidor principal # isocero.ogimet.com -> servidor secundario # aire.ogimet.com -> servidor de pruebas # # Los directorios que se necesitan sincronizar y sus archivos de finalización son: # # ficheros_gfs/ -------------------------------------- (no es necesario) # gfs_meteored/ finished_${FECHA}_${PASADA} (necesario) # gfs_meteored2/ finished_capas_${FECHA}_${PASADA} (necesario) # gfs_meteored3/ finished_mapas2_${FECHA}_${PASADA} (necesario) # gfs_meteoredM/ finished_capas_${FECHA}_${PASADA} (necesario) # gfs_meteoredM/ (XML) finished_capas_xml_${FECHA}_${PASADA} (necesario) # gfs_pruebas/ -------------------------------------- (no es necesario) # gfs_pruebas3/ -------------------------------------- (no es necesario) # json/ finished_plocales_json_${FECHA}_${PASADA} (necesario) # nas/ -------------------------------------- (no es necesario) # productos/ -------------------------------------- (no es necesario) # uvi_meteored/ finished_uvi_${FECHA} (necesario) # wrf_meteored2/ finished_capas_wrf_${FECHA}_${PASADA} (necesario) # wrf_meteored2/ (XML) finished_capas_xml_${FECHA}_${PASADA} (necesario) # wrf_meteored3/ finished_wrf2_${FECHA}_${PASADA} (necesario) # xml/ finished_plocales_xml_${FECHA}_${PASADA} (necesario) # # La raíz de los directorios a sincronizar es: "/home/imagenes.meteored.com" # La raíz de los archivos de finalización es: "/home/gfs/meteored" # # *** Este script asume que está instalado "Google Cloud SDK" en el servidor. # Los pasos de cómo instalar "Google Cloud SDK" y cómo inicializarlo están a continuación: # (+info: https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu ) # # Para instalar e inicializar el servicio gcloud apropiadamente: # 1. Instalar como root "Google Cloud SDK" mediante los siguientes comandos (obtenidos de https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu): # a) Instalar el paquete lsb-release y apt-transport-https : apt-get install lsb-release apt-transport-https # b) export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" # c) echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list # d) curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - # e) apt-get update && apt-get install google-cloud-sdk # # 2. Habilitar un servicio en gcloud mediante una clave JSON. # a) Pedir a meteored el archivo con la clave en formato JSON. # b) Ejecutar en la consola: # gcloud auth activate-service-account --key-file KEY_FILE # siendo KEY_FILE la ruta absoluta de la clave JSON. # # 3. Inicializar gcloud y configurarlo: # a) Ejecutar en consola: # gcloud init # b) Elegimos para usar la cuenta 1 que es la que hemos iniciado anteriormente. # c) Introducimos la ID del proyecto: # api-project-858154548956 # # 4. Comprobar que tenemos conexión con el Storage de Google Cloud. # a) Ejecutamos en consola: # gsutil ls # b) Comprobamos que se listan, al menos, los siguientes directorios: # ... # gs://mapas-meteored-europa-1/ # gs://mapas-meteored-europa-2/ # gs://mapas-meteored-europa-3/ # gs://mapas-meteored-usa-1/ # gs://mapas-meteored-usa-2/ # gs://mapas-meteored-usa-3/ # ... # 5. Llegados a este punto ya estamos en disposición de ejecutar el script mediante un cron del usuario gfs: # */10 5-11 * * * /usr/local/bin/google_cloud_storage 00 >> /home/gfs/meteored/google_cloud_storage.log # */10 17-23 * * * /usr/local/bin/google_cloud_storage 12 >> /home/gfs/meteored/google_cloud_storage.log # 0 0 1 * * rm /home/gfs/meteored/google_cloud_storage.log # # *** Si al hacer "gsutil rsync" aparecen comentarios acerca de "crcmod", tendremos # * que añadir esta funcionalidad de python. Si no aparecen comentarios, no # * deberemos hacer nada. # * # * El comando gsutil rsync comprueba primero la hora de creación de los archivos # * y su tamaño. Si no coinciden en los dos directorios que queremos sincronizar, # * entonces comienza a sincronizarlos. Si tanto la fecha como el tamaño coinciden, # * se puede usar el checksum de cada archivo para ver si son idénticos. Aquí es # * donde se usa la funcionalidad crcmod que es necesario instalar pues si no se # * vuelve el proceso mucho más lento. # * # * Para obtener información de cómo instalarlo podemos ejecutar "gsutil help crcmod", # * que nos dará la siguiente información: # * 1. Nos logueamos como root y ejecutamos los siguientes comandos en consola: # * a) apt-get install gcc python-dev python-setuptools # * b) easy_install -U pip # * c) pip uninstall crcmod # * d) pip install -U crcmod # * * * * * * * * * * * * # # Marcos Molina Cano - 29/08/2016 ################################################################################# # Comprobación de que los parámetros de entrada son apropiados. if test ${#} -eq 0 ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** Faltan parámetros de entrada." echo "" echo " Uso: google_cloud_storage.sh pasada" echo "" echo " pasada 00 para la pasada de las 00 UTC y 12 para la pasada de las 12 UTC" exit elif test ${#} -eq 1 ; then if test "${1}" = "00" -o "${1}" = "12"; then PASADA="${1}" FECHA=`date -u +%Y%m%d` else echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** El parámetro PASADA no coincide con 00 ni con 12." echo "" echo " Uso: google_cloud_storage.sh pasada" echo "" echo " pasada 00 para la pasada de las 00 UTC y 12 para la pasada de las 12 UTC" exit fi elif test ${#} -gt 1 ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** Demasiados parámetros de entrada." echo "" echo " Uso: google_cloud_storage.sh pasada" echo "" echo " pasada 00 para la pasada de las 00 UTC y 12 para la pasada de las 12 UTC" exit fi # Definición de binarios, archivos y directorios a usar. BINARIO="/usr/bin/gsutil" PRODUCT_DIR="/home/imagenes.meteored.com" FINISHED_DIR="/home/gfs/meteored" LOCK_FILE="${FINISHED_DIR}/google_cloud_storage.lock" topic="predicciones" # Lo primero es comprobar que el trabajo no se está realizando ya en otro PID # mediante la existencia del archivo de bloqueo. Además, se comprueba que no # tenga más de X horas. En el caso contrario es que algo ha fallado, se borra # el archivo de bloqueo, se crea uno nuevo y se reinicia el trabajo. if test -e ${LOCK_FILE} ; then if test `stat -c %Y ${LOCK_FILE}` -lt `date -u +%s --date="2 hour ago"` ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - Archivo de bloqueo demasiado antiguo. Borrando y creando..." touch ${LOCK_FILE} else echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - Archivo de bloqueo existente. Saliendo..." exit fi else echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - Inicio" touch ${LOCK_FILE} fi # Se comprueba que está instalado "Google Cloud SDK" en el servidor. if test ! -s ${BINARIO} ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error **** --> ${BINARIO} (Google Cloud SDK) no se encuentra o no existe. Saliendo..." exit fi # Se comprueba que existe el directorio donde están los archivos de finalización. if test ! -d ${FINISHED_DIR} ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** --> ${FINISHED_DIR} no existe. Saliendo..." exit fi # Se comprueba que existe el directorio donde están los productos a sincronizar. if test ! -d ${PRODUCT_DIR} ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** --> ${PRODUCT_DIR} no existe. Saliendo..." exit fi ################################################################################# ### Definición del servidor en el que nos encontramos. ### ################################################################################# # En las siguientes líneas se definen los tres servidores en los que se ejecuta # este script y más adelante se comprueba en cuál nos encontramos. # Si queremos probarlo en local, comentamos la línea con el hostname del servidor # y descomentamos la línea que la sustituye con "`hostname`". PRINCIPAL="tiempo.ogimet.com" #PRINCIPAL="`hostname`" SECUNDARIO="isocero.ogimet.com" #SECUNDARIO="`hostname`" PRUEBAS="aire.ogimet.com" #PRUEBAS="`hostname`" # Definición de los "buckets" necesarios (análogos a directorios) en el Storage de Google. # Los "buckets" del servidor ${PRINCIPAL} acabarán en "-1". # Los "buckets" del servidor ${SECUNDARIO} acabarán en "-2". # Los "buckets" del servidor ${PRUEBAS} acabarán en "-3". if test "`hostname`" = "${PRINCIPAL}" ; then servidor="principal" EU="gs://mapas-meteored-europa-1/" US="gs://mapas-meteored-usa-1/" bucket_shortname=("europa-1" "usa-1") elif test "`hostname`" = "${SECUNDARIO}" ; then servidor="secundario" EU="gs://mapas-meteored-europa-2/" US="gs://mapas-meteored-usa-2/" bucket_shortname=("europa-2" "usa-2") elif test "`hostname`" = "${PRUEBAS}" ; then servidor="pruebas" EU="gs://mapas-meteored-europa-3/" US="gs://mapas-meteored-usa-3/" bucket_shortname=("europa-3" "usa-3") else echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - *** Error *** 'hostname' no coincide con ${PRINCIPAL}, ${SECUNDARIO} ni ${PRUEBAS}." exit fi # Comprobación de que los "buckets" existen en el Storage de Google. # En caso de no existir se crean con la localización geográfica apropiada mediante la # la opcíon [-l loc] de gsutil. bucket=(${EU} ${US}) for ((i=0; i<${#bucket[@]}; i++)) ; do if test "`gsutil ls -b ${bucket[${i}]}`" != "${bucket[${i}]}" ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - WARNING --> ${bucket[${i}]} no existe en el Storage de Google. Creándolo..." if test "${bucket[${i}]}" = "${EU}" ; then loc="europe-west1" else loc="us-central1" fi # Se crea el "bucket" en la localización geográfica apropiada y # se le dan permisos a cualquier usuario para leer el archivo. gsutil mb -l ${loc} ${bucket[${i}]} gsutil defacl ch -u AllUsers:R ${bucket[${i}]} echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${bucket[${i}]} creado." fi done # El nombre de los archivos de finalización se guarda en una variable # de tipo XXXXX cuya raíz está en ${CHECK_DIR}. # El nombre de los directorios donde están los productos se guardan # en una variable de tipo XXXXX_DIR cuya ráiz es ${PRODUCT_DIR} MAPAS="finished_${FECHA}_${PASADA}" MAPAS_DIR="gfs_meteored" CAPAS="finished_capas_${FECHA}_${PASADA}" CAPAS_DIR="gfs_meteored2" MAPAS2="finished_mapas2_${FECHA}_${PASADA}" MAPAS2_DIR="gfs_meteored3" CAPAS_MERCATOR="finished_capas_${FECHA}_${PASADA}" CAPAS_MERCATOR_DIR="gfs_meteoredM" CAPAS_MERCATOR_XML="finished_capas_xml_${FECHA}_${PASADA}" CAPAS_MERCATOR_XML_DIR="gfs_meteoredM" JSON="finished_plocales_json_${FECHA}_${PASADA}" JSON_DIR="json" UVI="finished_uvi_${FECHA}" UVI_DIR="uvi_meteored" CAPAS_WRF="finished_capas_wrf_${FECHA}_${PASADA}" CAPAS_WRF_DIR="wrf_meteored2" CAPAS_WRF_XML="finished_capas_xml_${FECHA}_${PASADA}" CAPAS_WRF_XML_DIR="wrf_meteored2" CAPAS_WRF2="finished_wrf2_${FECHA}_${PASADA}" CAPAS_WRF2_DIR="wrf_meteored3" XML="finished_plocales_xml_${FECHA}_${PASADA}" XML_DIR="xml" # Array con los archivos de finalización de cada producto. producto=(${MAPAS} ${CAPAS} ${MAPAS2} ${CAPAS_MERCATOR} ${CAPAS_MERCATOR_XML} \ ${JSON} ${UVI} ${CAPAS_WRF} ${CAPAS_WRF_XML} ${CAPAS_WRF2} ${XML}) # Array con los directorios donde se encuentran los productos. producto_dir=(${MAPAS_DIR} ${CAPAS_DIR} ${MAPAS2_DIR} ${CAPAS_MERCATOR_DIR} ${CAPAS_MERCATOR_XML_DIR} \ ${JSON_DIR} ${UVI_DIR} ${CAPAS_WRF_DIR} ${CAPAS_WRF_XML_DIR} ${CAPAS_WRF2_DIR} ${XML_DIR}) # Array con los nombres legibles de los directorios y su relación con el producto. # Únicamente se usa para que el archivo .log sea legible. producto_dir_name=("${MAPAS_DIR} " "${CAPAS_DIR} " "${MAPAS2_DIR} " "${CAPAS_MERCATOR_DIR} " "${CAPAS_MERCATOR_XML_DIR}_xml" \ "${JSON_DIR} " "${UVI_DIR} " "${CAPAS_WRF_DIR} " "${CAPAS_WRF_XML_DIR}_xml" "${CAPAS_WRF2_DIR} " "${XML_DIR} ") ################################################################################# ### BUCLE PRINCIPAL ### ################################################################################# # Bucle que recorre el directorio donde se guardan los archivos de finalización. # En caso de que exista uno, se lanza la sincronización del directorio con Storage de Google Cloud. for ((i=0; i<${#producto[@]}; i++)) ; do # Definición del archivo de finalización para el producto i-ésimo y comprobación de que existe. PRODUCT_FINISHED_FILE="${FINISHED_DIR}/${producto[${i}]}" if test ! -e ${PRODUCT_FINISHED_FILE}; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> Aún no disponible." continue fi # Empezamos el bucle para los buckets existentes en Google Cloud Storage. for ((j=0; j<${#bucket[@]}; j++)) ; do # Definición del archivo de bloqueo y de finalización por producto y bucket. GOOGLE_LOCK_FILE="${FINISHED_DIR}/${producto_dir[${i}]}_GoogleStorage_${bucket_shortname[${j}]}.lock" GOOGLE_FINISHED_FILE="${FINISHED_DIR}/${producto[${i}]}_GoogleStorage_${bucket_shortname[${j}]}" # Comprobación por si las tareas para este producto en este bucket ya se han realizado. if test -e ${GOOGLE_FINISHED_FILE} ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> ${bucket_shortname[${j}]}: Sincronizado." continue fi # Si llega aquí, se puede empezar la tarea para este producto en este bucket. echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> ${bucket_shortname[${j}]}: Comienza la sincronización... " # Preparamos el terreno: comprobamos que no haya ningún enlace simbólico roto. Si existe, # lo borramos, pues el comando rsync no comienza si encuentra alguno. for n in ${PRODUCT_DIR}/${producto_dir[${i}]}/*; do if test ! -f ${n}; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> ${bucket_shortname[${j}]}: Se elimina enlace simbólico roto -> ${n}." rm ${n} fi done # Se crea un archivo de bloqueo parcial, acto seguido se sincroniza el directorio con el Storage y después se borra # el archivo de bloqueo parcial. # Además, se cambia la hora del archivo de bloqueo total para reiniciar el contador de las X horas. touch ${LOCK_FILE} \ && touch ${GOOGLE_LOCK_FILE} \ && gsutil -m -h "Cache-Control:public, max-age=43200" rsync -d -r ${PRODUCT_DIR}/${producto_dir[${i}]}/ ${bucket[${j}]}${producto_dir[${i}]}/ > /dev/null 2>&1 \ && rm ${GOOGLE_LOCK_FILE} # En caso de que aún exista el archivo de bloqueo parcial, el proceso ha fallado. Si no existe, ha ido bien. if test ! -e ${GOOGLE_LOCK_FILE} ; then echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> ${bucket_shortname[${j}]}: ...sincronizado." gcloud alpha pubsub topics publish predicciones "${producto_dir[${i}]}" \ --attribute "servidor=${servidor}","bucket=${bucket_shortname[${j}]}","fecha=${FECHA}","pasada=${PASADA}","hora=`date -u +%Y-%m-%d_%H:%M:%S`" touch ${GOOGLE_FINISHED_FILE} else echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir_name[${i}]} --> ${bucket_shortname[${j}]}: Problemas con la sincronización. Siguiente..." rm ${GOOGLE_LOCK_FILE} continue fi done # echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - ${producto_dir[i]} --> Fin de la sincronización en Google Storage." done # Se borran los archivos de finalización de Google Storage que tengan más de dos días. FECHA_LIMITE=`date -u +%Y%m%d --date="2 days ago"` rm -f ${FINISHED_DIR}/finished_${FECHA_LIMITE}_${PASADA}_GoogleStorage_* # Tareas terminadas. Se borra el archivo de bloqueo. rm ${LOCK_FILE} echo "[`date -u +%Y-%m-%d` `date -u +%H:%M:%S` UTC] (PID $$) - Fin" echo ""