From 602b79980e22424d14f19a0416141686488e6a84 Mon Sep 17 00:00:00 2001 From: John-Paul Navarro Date: Wed, 18 Nov 2020 08:00:46 -0600 Subject: [PATCH] Upgrade to Manage-Resources-V3 and Manage-Glue2 like --- bin/route_rdr.py | 232 +++++++++++++++++++------------------- sbin/route_rdr.rc | 102 ----------------- sbin/route_rdr.service | 8 +- sbin/route_rdr.service.sh | 45 ++++++++ sbin/route_rdr.sh | 84 +++++++++++--- sbin/route_rdr.sysconfig | 11 +- 6 files changed, 235 insertions(+), 247 deletions(-) delete mode 100755 sbin/route_rdr.rc create mode 100755 sbin/route_rdr.service.sh diff --git a/bin/route_rdr.py b/bin/route_rdr.py index 677fb11..7749059 100755 --- a/bin/route_rdr.py +++ b/bin/route_rdr.py @@ -3,26 +3,21 @@ # Process RDR2 information from a source (http, https, file) to a destination (analyze display, file, warehouse) # Can also subscribe from AMQP # -# TODO: -# ... +import argparse +from datetime import datetime, tzinfo, timedelta +import http.client as httplib +import json +import logging +import logging.handlers import os +from pid import PidFile import pwd import re import sys -import argparse -import logging -import logging.handlers +import shutil import signal -import datetime -from datetime import datetime, tzinfo, timedelta -from time import sleep -try: - import http.client as httplib -except ImportError: - import httplib -import json import ssl -import shutil +from time import sleep import django django.setup() @@ -32,7 +27,6 @@ from rdr_db.models import * from processing_status.process import ProcessingActivity -from daemon import runner import pdb class UTC(tzinfo): @@ -44,85 +38,97 @@ def dst(self, dt): return timedelta(0) utc = UTC() -class HandleRDR(): - def __init__(self): - self.args = None - self.config = {} - self.src = {} - self.dest = {} - for var in ['uri', 'scheme', 'path', 'display']: # Where contains : - self.src[var] = None - self.dest[var] = None - self.peak_sleep = 10 * 60 # 10 minutes in seconds during peak business hours - self.off_sleep = 60 * 60 # 60 minutes in seconds during off hours - self.max_stale = 24 * 60 * 60 # 24 hours in seconds force refresh - # These attributes have their own database column - # Some fields exist in both parent and sub-resources, while others only in one - # Those in one will be left empty in the other, or inherit from the parent - self.have_column = ['resource_id', 'info_resourceid', - 'resource_descriptive_name', 'resource_description', - 'project_affiliation', 'provider_level', - 'resource_status', 'current_statuses', 'updated_at'] - - default_file = 'file:./rdr.json' +# Used during initialization before loggin is enabled +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) +class Router(): + def __init__(self): parser = argparse.ArgumentParser(epilog='File SRC|DEST syntax: file: contains : + self.src[var] = None + self.dest[var] = None + self.peak_sleep = 10 * 60 # 10 minutes in seconds during peak business hours + self.off_sleep = 60 * 60 # 60 minutes in seconds during off hours + self.max_stale = 24 * 60 * 60 # 24 hours in seconds force refresh + # These attributes have their own database column + # Some fields exist in both parent and sub-resources, while others only in one + # Those in one will be left empty in the other, or inherit from the parent + self.have_column = ['resource_id', 'info_resourceid', + 'resource_descriptive_name', 'resource_description', + 'project_affiliation', 'provider_level', + 'resource_status', 'current_statuses', 'updated_at'] + default_file = 'file:./rdr.json' + # Verify arguments and parse compound arguments if not getattr(self.args, 'src', None): # Tests for None and empty '' if 'RDR_INFO_URL' in self.config: @@ -168,21 +174,38 @@ def __init__(self): self.logger.error('Source and Destination can not both be a {file}') sys.exit(1) - if self.args.daemon_action: - self.stdin_path = '/dev/null' - if 'LOG_FILE' in self.config: - self.stdout_path = self.config['LOG_FILE'].replace('.log', '.daemon.log') - self.stderr_path = self.stdout_path - else: - self.stdout_path = '/dev/tty' - self.stderr_path = '/dev/tty' - self.SaveDaemonLog(self.stdout_path) - self.pidfile_timeout = 5 - if 'PID_FILE' in self.config: - self.pidfile_path = self.config['PID_FILE'] - else: - name = os.path.basename(__file__).replace('.py', '') - self.pidfile_path = '/var/run/{}/{}.pid'.format(name ,name) + if self.args.daemonaction == 'start': + if self.src['scheme'] not in ['http', 'https'] or self.dest['scheme'] not in ['warehouse']: + self.logger.error('Can only daemonize when source=[http|https] and destination=warehouse') + sys.exit(1) + + self.logger.info('Source: ' + self.src['display']) + self.logger.info('Destination: ' + self.dest['display']) + self.logger.info('Config: ' + self.config_file) + + def SaveDaemonStdOut(self, path): + # Save daemon log file using timestamp only if it has anything unexpected in it + try: + file = open(path, 'r') + lines = file.read() + file.close() + if not re.match("^started with pid \d+$", lines) and not re.match("^$", lines): + ts = datetime.strftime(datetime.now(), '%Y-%m-%d_%H:%M:%S') + newpath = '{}.{}'.format(path, ts) + self.logger.debug('Saving previous daemon stdout to {}'.format(newpath)) + shutil.copy(path, newpath) + except Exception as e: + self.logger.error('Exception in SaveDaemonStdOut({})'.format(path)) + return + + def exit_signal(self, signum, frame): + self.logger.critical('Caught signal={}({}), exiting with rc={}'.format(signum, signal.Signals(signum).name, signum)) + sys.exit(signum) + + def exit(self, rc): + if rc: + self.logger.error('Exiting with rc={}'.format(rc)) + sys.exit(rc) def Retrieve_RDR(self, url): idx = url.find(':') @@ -442,25 +465,6 @@ def latest_status_date(self, resource_status_dates, current_status, which_date): except: return(None) - def SaveDaemonLog(self, path): - # Save daemon log file using timestamp only if it has anything unexpected in it - try: - with open(path, 'r') as file: - lines=file.read() - file.close() - if not re.match("^started with pid \d+$", lines) and not re.match("^$", lines): - ts = datetime.strftime(datetime.now(), '%Y-%m-%d_%H:%M:%S') - newpath = '{}.{}'.format(path, ts) - shutil.copy(path, newpath) - print('SaveDaemonLog as {}'.format(newpath)) - except Exception as e: - print('Exception in SaveDaemonLog({})'.format(path)) - return - - def exit_signal(self, signal, frame): - self.logger.critical('Caught signal={}, exiting...'.format(signal)) - sys.exit(0) - def smart_sleep(self, last_run): # This functions sleeps, performs refresh checks, and returns when it's time to refresh while True: @@ -497,13 +501,7 @@ def smart_sleep(self, last_run): self.logger.error('{} parsing last_update_time={}: {}'.format(type(e).__name__, ts_json['last_update_time'], e.message)) last_db_update = None - def run(self): - signal.signal(signal.SIGINT, self.exit_signal) - signal.signal(signal.SIGTERM, self.exit_signal) - self.logger.info('Starting program={} pid={}, uid={}({})'.format(os.path.basename(__file__), os.getpid(), os.geteuid(), pwd.getpwuid(os.geteuid()).pw_name)) - self.logger.info('Source: ' + self.src['display']) - self.logger.info('Destination: ' + self.dest['display']) - + def Run(self): while True: self.start = datetime.now(utc) self.stats = { @@ -538,24 +536,22 @@ def run(self): pa.FinishActivity(rc, summary_msg) else: # Something failed, use returned message pa.FinishActivity(rc, warehouse_msg) - if not self.args.daemon_action: + if not self.args.daemonaction: break self.smart_sleep(self.start) +########## CUSTOMIZATIONS END ########## + if __name__ == '__main__': - router = HandleRDR() - if router.args.daemon_action is None: # Interactive execution - myrouter = router.run() - sys.exit(0) - - if router.args.daemon_action == 'start': - if router.src['scheme'] not in ['http', 'https'] or router.dest['scheme'] not in ['warehouse']: - router.logger.error('Can only daemonize when source=[http|https] and destination=warehouse') - sys.exit(1) + router = Router() + with PidFile(router.pidfile_path): + try: + router.Setup() + rc = router.Run() + except Exception as e: + msg = '{} Exception: {}'.format(type(e).__name__, e) + router.logger.error(msg) + traceback.print_exc(file=sys.stdout) + rc = 1 + router.exit(rc) - # Daemon execution - router.logger.info('Daemon startup') - daemon_runner = runner.DaemonRunner(router) - daemon_runner.daemon_context.files_preserve=[router.handler.stream] - daemon_runner.daemon_context.working_directory=router.config['RUN_DIR'] - daemon_runner.do_action() diff --git a/sbin/route_rdr.rc b/sbin/route_rdr.rc deleted file mode 100755 index cab37b9..0000000 --- a/sbin/route_rdr.rc +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh - -### BEGIN INIT INFO -# Provides: route_rdr -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Route RDR information from a source to a destination -# Description: Route RDR information from a {file, directory, amqp} to a {directory, api} -### END INIT INFO - -# Change these lines to suit where you install your script and what you want to call it -DAEMON_NAME=route_rdr -APP_BASE=/soft/warehouse-apps-1.0/Manage-RDR -APP_SOURCE=${APP_BASE}/PROD -WAREHOUSE_SOURCE=/soft/warehouse-1.0/PROD -DAEMON_BIN=${APP_SOURCE}/bin/${DAEMON_NAME}.py -DAEMON_LOG=${APP_BASE}/var/${DAEMON_NAME}.daemon.log -if [[ "$2" != --pdb && "$3" != --pdb && "$4" != --pdb ]]; then - exec >${DAEMON_LOG} 2>&1 -fi - -# Add any command line options for your daemon here -DAEMON_OPTS="-l info -c ${APP_BASE}/conf/${DAEMON_NAME}.conf" - -# This next line determines what user the script runs as -DAEMON_USER=software - -. /etc/init.d/functions - -PYTHON_BASE=${APP_BASE}/`cat python/lib/python*/orig-prefix.txt` -export LD_LIBRARY_PATH=${PYTHON_BASE}/lib - -PIPENV_BASE=${APP_BASE}/python -source ${PIPENV_BASE}/bin/activate - -PYTHON_BIN=python3 - -export PYTHONPATH=${APP_SOURCE}/lib:${WAREHOUSE_SOURCE}/django_xsede_warehouse -export DJANGO_CONF=${APP_BASE}/conf/django_xsede_warehouse.conf -export DJANGO_SETTINGS_MODULE=xsede_warehouse.settings - -do_start () { - echo -n "Starting ${DAEMON_NAME}:" - if [ `id -u` = 0 ] ; then - su ${DAEMON_USER} -s /bin/sh -c "${PYTHON_BIN} ${DAEMON_BIN} start ${DAEMON_OPTS}" - RETVAL=$? - elif [ `id -u` = `id -u ${DAEMON_USER}` ] ; then - ${PYTHON_BIN} ${DAEMON_BIN} start ${DAEMON_OPTS} - RETVAL=$? - else - echo "Only root or ${DAEMON_USER} should run ${DAEMON_BIN}" - RETVAL=99 - fi -} -do_stop () { - echo -n "Stopping ${DAEMON_NAME}:" - if [ `id -u` = 0 ] ; then - su ${DAEMON_USER} -s /bin/sh -c "${PYTHON_BIN} ${DAEMON_BIN} stop ${DAEMON_OPTS}" - RETVAL=$? - elif [ `id -u` = `id -u ${DAEMON_USER}` ] ; then - ${PYTHON_BIN} ${DAEMON_BIN} stop ${DAEMON_OPTS} - RETVAL=$? - else - echo "Only root or ${DAEMON_USER} should run ${DAEMON_BIN}" - RETVAL=99 - fi -} -do_debug () { - echo -n "Debugging: ${PYTHON_BIN} ${DAEMON_BIN} $@ ${DAEMON_OPTS}" - ${PYTHON_BIN} ${DAEMON_BIN} $@ ${DAEMON_OPTS} - RETVAL=$? -} - -case "$1" in - - start|stop) - do_${1} - ;; - - debug) - do_debug ${@:2} - ;; - - restart|reload|force-reload) - do_stop - do_start - ;; - - status) - echo "Haven't implemented status" - ;; - - *) - echo "Usage: /etc/init.d/${DAEMON_NAME} {start|stop|restart|status}" - exit 1 - ;; - -esac -echo rc=$RETVAL -exit $RETVAL diff --git a/sbin/route_rdr.service b/sbin/route_rdr.service index 9f977c8..fb45c32 100644 --- a/sbin/route_rdr.service +++ b/sbin/route_rdr.service @@ -3,13 +3,9 @@ Description=Information Services RDR Router After=network.target remote-fs.target nss-lookup.target [Service] -Type=forking +Type=simple EnvironmentFile=/soft/warehouse-apps-1.0/Manage-RDR/PROD/sbin/route_rdr.sysconfig -ExecStart=/soft/warehouse-apps-1.0/Manage-RDR/PROD/sbin/route_rdr.sh start -ExecReload=/soft/warehouse-apps-1.0/Manage-RDR/PROD/sbin/route_rdr.sh restart -ExecStop=/soft/warehouse-apps-1.0/Manage-RDR/PROD/sbin/route_rdr.sh stop -PIDFile=/soft/warehouse-apps-1.0/Manage-RDR/var/route_rdr.pid -#PrivateTmp=true +ExecStart=/soft/warehouse-apps-1.0/Manage-RDR/PROD/sbin/route_rdr.service.sh start User=software Group=admin Restart=always diff --git a/sbin/route_rdr.service.sh b/sbin/route_rdr.service.sh new file mode 100755 index 0000000..ea253f4 --- /dev/null +++ b/sbin/route_rdr.service.sh @@ -0,0 +1,45 @@ +#!/bin/sh +do_start () { + echo -n "Starting ${APP_NAME}:" + export LD_LIBRARY_PATH=${PYTHON_BASE}/lib + source ${PIPENV_BASE}/bin/activate + exec ${PYTHON_BIN} ${APP_BIN} start ${APP_OPTS} + RETVAL=$? +} +do_stop () { + echo -n "Stopping ${APP_NAME}:" + export LD_LIBRARY_PATH=${PYTHON_BASE}/lib + source ${PIPENV_BASE}/bin/activate + exec ${PYTHON_BIN} ${APP_BIN} stop ${APP_OPTS} + RETVAL=$? +} +do_debug () { + echo -n "Debugging: ${PIPENV_BASE}/bin/python ${APP_BIN} -l debug $@ ${APP_OPTS}" + export LD_LIBRARY_PATH=${PYTHON_BASE}/lib + source ${PIPENV_BASE}/bin/activate + exec ${PYTHON_BIN} ${APP_BIN} -l debug $@ ${APP_OPTS} + RETVAL=$? +} + +case "$1" in + start|stop) + do_${1} ${@:2} + ;; + + debug) + do_debug ${@:2} + ;; + + restart|reload|force-reload) + do_stop + do_start + ;; + + *) + echo "Usage: ${APP_NAME} {start|stop|debug|restart}" + exit 1 + ;; + +esac +echo rc=$RETVAL +exit $RETVAL diff --git a/sbin/route_rdr.sh b/sbin/route_rdr.sh index 8172f8a..69cf06a 100755 --- a/sbin/route_rdr.sh +++ b/sbin/route_rdr.sh @@ -1,29 +1,81 @@ #!/bin/sh + +### BEGIN INIT INFO +# Provides: route_rdr +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Route RDR information from a source to a destination +# Description: Route RDR information from a {file, directory, amqp} to a {directory, api} +### END INIT INFO + +####### Customizations START ####### +APP_NAME=route_rdr +APP_BASE=/soft/warehouse-apps-1.0/Manage-RDR +WAREHOUSE_BASE=/soft/warehouse-1.0 +# Override in shell environment +if [ -z "$PYTHON_BASE" ]; then + PYTHON_BASE=/soft/python/python-3.7.7-base +fi +DAEMON_USER=software +####### Customizations END ####### + +####### Everything else should be standard ####### +APP_SOURCE=${APP_BASE}/PROD +WAREHOUSE_SOURCE=${WAREHOUSE_BASE}/PROD + +APP_LOG=${APP_BASE}/var/${APP_NAME}.daemon.log +if [[ "$1" != --pdb && "$2" != --pdb && "$3" != --pdb && "$4" != --pdb ]]; then + exec >${APP_LOG} 2>&1 +fi + +APP_BIN=${APP_SOURCE}/bin/${APP_NAME}.py +APP_OPTS="-l info -c ${APP_BASE}/conf/${APP_NAME}.conf" + +PYTHON_BIN=python3 +export LD_LIBRARY_PATH=${PYTHON_BASE}/lib +source ${APP_BASE}/python/bin/activate + +export PYTHONPATH=${APP_SOURCE}/lib:${WAREHOUSE_SOURCE}/django_xsede_warehouse +export DJANGO_CONF=${APP_BASE}/conf/django_xsede_warehouse.conf +export DJANGO_SETTINGS_MODULE=xsede_warehouse.settings + do_start () { - echo -n "Starting ${DAEMON_NAME}:" - export LD_LIBRARY_PATH=${PYTHON_BASE}/lib - source ${PIPENV_BASE}/bin/activate - ${PYTHON_BIN} ${DAEMON_BIN} start ${DAEMON_OPTS} - RETVAL=$? + echo -n "Starting ${APP_NAME}:" + if [ `id -u` = 0 ] ; then + su ${DAEMON_USER} -s /bin/sh -c "${PYTHON_BIN} ${APP_BIN} start ${APP_OPTS}" + RETVAL=$? + elif [ `id -u` = `id -u ${DAEMON_USER}` ] ; then + ${PYTHON_BIN} ${APP_BIN} start ${APP_OPTS} + RETVAL=$? + else + echo "Only root or ${DAEMON_USER} should run ${APP_BIN}" + RETVAL=99 + fi } do_stop () { - echo -n "Stopping ${DAEMON_NAME}:" - export LD_LIBRARY_PATH=${PYTHON_BASE}/lib - source ${PIPENV_BASE}/bin/activate - ${PYTHON_BIN} ${DAEMON_BIN} stop ${DAEMON_OPTS} - RETVAL=$? + echo -n "Stopping ${APP_NAME}:" + if [ `id -u` = 0 ] ; then + su ${DAEMON_USER} -s /bin/sh -c "${PYTHON_BIN} ${APP_BIN} stop ${APP_OPTS}" + RETVAL=$? + elif [ `id -u` = `id -u ${DAEMON_USER}` ] ; then + ${PYTHON_BIN} ${APP_BIN} stop ${APP_OPTS} + RETVAL=$? + else + echo "Only root or ${DAEMON_USER} should run ${APP_BIN}" + RETVAL=99 + fi } do_debug () { - echo -n "Debugging: ${PYTHON_BIN} ${DAEMON_BIN} $@ ${DAEMON_OPTS}" - export LD_LIBRARY_PATH=${PYTHON_BASE}/lib - source ${PIPENV_BASE}/bin/activate - ${PYTHON_BIN} ${DAEMON_BIN} $@ ${DAEMON_OPTS} + echo -n "Debugging: ${PYTHON_BIN} ${APP_BIN} $@ ${APP_OPTS}" + ${PYTHON_BIN} ${APP_BIN} $@ ${APP_OPTS} RETVAL=$? } case "$1" in start|stop) - do_${1} + do_${1} ${@:2} ;; debug) @@ -40,7 +92,7 @@ case "$1" in ;; *) - echo "Usage: /etc/init.d/${DAEMON_NAME} {start|stop|restart|status}" + echo "Usage: ${APP_NAME} {start|stop|debug|restart} []" exit 1 ;; diff --git a/sbin/route_rdr.sysconfig b/sbin/route_rdr.sysconfig index 00fadf4..a38b884 100644 --- a/sbin/route_rdr.sysconfig +++ b/sbin/route_rdr.sysconfig @@ -1,12 +1,13 @@ -DAEMON_NAME=route_rdr -DAEMON_BIN=/soft/warehouse-apps-1.0/Manage-RDR/PROD/bin/route_rdr.py +APP_NAME=route_rdr +APP_BASE=/soft/warehouse-apps-1.0/Manage-RDR +APP_BIN=/soft/warehouse-apps-1.0/Manage-RDR/PROD/bin/route_rdr.py # Add any command line options for your daemon here -DAEMON_OPTS="-l info -c /soft/warehouse-apps-1.0/Manage-RDR/conf/route_rdr.conf" +APP_OPTS="-l info -c /soft/warehouse-apps-1.0/Manage-RDR/conf/route_rdr.conf" -APP_BASE=/soft/warehouse-apps-1.0/Manage-RDR -PYTHON_BASE=/soft/python/python-3.7.5-base +PYTHON_BASE=/soft/python/python-3.7.7-base PIPENV_BASE=/soft/warehouse-apps-1.0/Manage-RDR/python PYTHON_BIN=python3 PYTHONPATH=/soft/warehouse-apps-1.0/Manage-RDR/PROD/lib:/soft/warehouse-1.0/PROD/django_xsede_warehouse + DJANGO_CONF=/soft/warehouse-apps-1.0/Manage-RDR/conf/django_xsede_warehouse.conf DJANGO_SETTINGS_MODULE=xsede_warehouse.settings