#!/usr/bin/env python from __future__ import print_function import sys # check the system python version and require 2.7.x or greater if sys.hexversion < 0x02070000: print(70 * "*") print("ERROR: {0} requires python >= 2.7.x. ".format(sys.argv[0])) print("It appears that you are running python {0}".format( ".".join(str(x) for x in sys.version_info[0:3]))) print(70 * "*") sys.exit(1) # # built-in modules # import argparse import errno import glob import os import subprocess import traceback try: import lxml.etree as etree except: import xml.etree.ElementTree as etree # get the postprocess virtualenv path from the env_postprocess.xml file env_file = './env_postprocess.xml' postprocess_path = '' standalone = '' if os.path.isfile(env_file): xml_tree = etree.ElementTree() xml_tree.parse(env_file) for entry_tag in xml_tree.findall('entry'): if entry_tag.get('id') == 'POSTPROCESS_PATH': postprocess_path = entry_tag.get('value') if entry_tag.get('id') == 'STANDALONE': standalone = entry_tag.get('value') else: err_msg = ('copy_html ERROR: env_postprocess.xml does not exist in this directory.') raise OSError(err_msg) # check if virtualenv is activated if hasattr(sys, 'real_prefix'): try: import cesm_utils except: activate_file = '{0}/cesm-env2/bin/activate_this.py'.format(postprocess_path) if not os.path.isfile(activate_file): err_msg = ('copy_html ERROR: the virtual environment in {0} does not exist.'.format(postprocess_path) \ + 'Please run {0}/create_python_env.sh -cimeroot [cimeroot] -machine [machine name]'.format(postprocess_path)) raise OSError(err_msg) try: execfile(activate_file, dict(__file__=activate_file)) except: raise OSError('copy_html ERROR: Unable to activate python virtualenv {0}'.format(activate_file)) else: activate_file = '{0}/cesm-env2/bin/activate_this.py'.format(postprocess_path) if not os.path.isfile(activate_file): err_msg = ('copy_html ERROR: the virtual environment in {0} does not exist.'.format(postprocess_path) \ + 'Please run {0}/create_python_env.sh -cimeroot [cimeroot] -machine [machine name]'.format(postprocess_path)) raise OSError(err_msg) try: execfile(activate_file, dict(__file__=activate_file)) except: raise OSError('copy_html ERROR: Unable to activate python virtualenv {0}'.format(activate_file)) if sys.version_info[0] == 2: from ConfigParser import SafeConfigParser as config_parser else: from configparser import ConfigParser as config_parser from cesm_utils import cesmEnvLib import jinja2 #======================================================================= # commandline_options - parse any command line options #======================================================================= def commandline_options(): """Process the command line arguments. """ parser = argparse.ArgumentParser( description='Read the necessary XML files from the caseroot postprocessing configuration and secure copy the html files and diagnostics plot files to a remote webserver.') parser.add_argument('-backtrace', '--backtrace', action='store_true', help='show exception backtraces as extra debugging ' 'output') parser.add_argument('-debug', '--debug', nargs=1, required=False, type=int, default=0, help='debugging verbosity level output: 0 = none, 1 = minimum, 2 = maximum. 0 is default') parser.add_argument('--use-ssh-key', dest='use_ssh_key', action='store_true', help='Use a ssh key to connect to the remove web host defined by '\ 'XML variables "GLOBAL_WEBHOST" and "GLOBAL_WEBLOGIN". '\ 'If a ssh key is not present, then this option will cause '\ 'execution to stop as opposed to issuing a warning '\ 'and prompting for a password multiple times. '\ 'More details about how to create ssh keys is available at '\ '"http://tools.cgd.ucar.edu/make_user_ssh_keys/index.html"') options = parser.parse_args() return options #======================================================================= # get_years #======================================================================= def get_years(env, comp): """ get_years - get the start and stop years for diagnostics based on component Arguments: env (dict) - dictionary of env variables comp (string) - component name Return: model_start_year (string) - model start year for diagnostics model_stop_year (string) - model stop year for diagnostics control_start_year (string) - control start year for diagnostics control_stop_year (string) - control stop year for diagnostics trends_start_year1 (string) - model trends start year for diagnostics trends_stop_year1 (string) - model trends stop year for diagnostics trends_start_year2 (string) - control trends start year for diagnostics trends_stop_year2 (string) - control trends stop year for diagnostics """ # define component-year mapping for model comp_lookup = {'atm' : {'start_year':'ATMDIAG_test_first_yr', 'num_years':'ATMDIAG_test_nyrs'}, 'ice' : {'start_year':'ICEDIAG_BEGYR_CONT', 'stop_year':'ICEDIAG_ENDYR_CONT'}, 'lnd' : {'start_year':'LNDDIAG_clim_first_yr_1', 'num_years':'LNDDIAG_clim_num_yrs_1'}, 'ocn' : {'start_year':'OCNDIAG_YEAR0', 'stop_year':'OCNDIAG_YEAR1'}} # define component-year mapping for control comp_lookup_control = {'atm' : {'start_year':'ATMDIAG_cntl_first_yr', 'num_years':'ATMDIAG_cntl_nyrs'}, 'ice' : {'start_year':'ICEDIAG_BEGYR_DIFF', 'stop_year':'ICEDIAG_ENDYR_DIFF'}, 'lnd' : {'start_year':'LNDDIAG_clim_first_yr_2', 'num_years':'LNDDIAG_clim_num_yrs_2'}, 'ocn' : {'start_year':'OCNDIAG_CNTRLYEAR0', 'stop_year':'OCNDIAG_CNTRLYEAR1'}} # define component-year mapping for trends and timeseries comp_lookup_trends = {'atm' : {}, 'ice' : {}, 'lnd' : {'start_year1':'LNDDIAG_trends_first_yr_1', 'num_years1':'LNDDIAG_trends_num_yrs_1', 'start_year2':'LNDDIAG_trends_first_yr_2', 'num_years2':'LNDDIAG_trends_num_yrs_2'}, 'ocn' : {'start_year':'OCNDIAG_TSERIES_YEAR0', 'stop_year':'OCNDIAG_TSERIES_YEAR1'}} # get the model years comp_data = comp_lookup[comp] model_start_year = '{0}'.format(env[comp_data['start_year']]) if comp_data.has_key('num_years'): model_stop_year = '{0}'.format(int(model_start_year) + int(env[comp_data['num_years']])) else: model_stop_year = '{0}'.format(env[comp_data['stop_year']]) # get the control years comp_data = comp_lookup_control[comp] control_start_year = '{0}'.format(env[comp_data['start_year']]) control_stop_year = None if comp_data.has_key('num_years'): # check that the strings are not empty if len(control_start_year) > 0 and len(env[comp_data['num_years']]) > 0: control_stop_year = '{0}'.format(int(control_start_year) + int(env[comp_data['num_years']])) else: control_stop_year = '{0}'.format(env[comp_data['stop_year']]) # get the trends years comp_data = comp_lookup_trends[comp] trends_start_year1 = trends_stop_year1 = trends_start_year2 = trends_stop_year2 = None if comp == 'ocn': trends_start_year1 = '{0}'.format(env[comp_data['start_year']]) trends_stop_year1 = '{0}'.format(env[comp_data['stop_year']]) elif comp == 'lnd': trends_start_year1 = '{0}'.format(env[comp_data['start_year1']]) if len(trends_start_year1) > 0 and len(env[comp_data['num_years1']]) > 0: trends_stop_year1 = '{0}'.format(int(trends_start_year1) + int(env[comp_data['num_years1']])) trends_start_year2 = '{0}'.format(env[comp_data['start_year2']]) if len(trends_start_year2) > 0 and len(env[comp_data['num_years2']]) > 0: trends_stop_year2 = '{0}'.format(int(trends_start_year2) + int(env[comp_data['num_years2']])) return model_start_year, model_stop_year, control_start_year, control_stop_year, \ trends_start_year1, trends_stop_year1, trends_start_year2, trends_stop_year2 #======================================================================= # check_ssh_key #======================================================================= def check_ssh_key(env, use_ssh_key): # check if ssh key is set for passwordless access to the web host try: output = subprocess.check_output( "ssh -oNumberOfPasswordPrompts=0 {0}@{1} 'echo hello'".format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST']), stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: if use_ssh_key: print('ERROR: unable to connect to remote web host {0}@{1} without a password'.format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'])) sys.exit(1) else: print('WARNING: unable to connect to remote web host {0}@{1} without a password'.format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'])) print(' You will be prompted for a password multiple times in order to copy the files.') #======================================================================= # create_top_level #======================================================================= def create_top_level(env, comp): # make sure top level remote directory exists try: pipe = subprocess.Popen( ["ssh {0}@{1} 'mkdir -p {2}/{3}'".format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp)], shell=True) pipe.wait() except Exception as e: print('ERROR: unable to create remote directory {0}@{1}:{2}/{3}'.format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp)) print(' {0} - {1}'.format(e.errno, e.strerror)) sys.exit(1) #======================================================================= # scp_files - scp files to a remote server #======================================================================= def scp_files(env, local, remote): try: pipe = subprocess.Popen( ['scp -r {0} {1}'.format(local, remote)], shell=True) pipe.wait() return True except Exception as e: print('copy_html WARNING: scp command failed with error:') print(' {0} - {1}'.format(e.errno, e.strerror)) return False #======================================================================= # read_paths - get the paths from the web_dirs files #======================================================================= def read_paths(env, comp_data): """ read the $PP_CASE_PATH/web_dirs files specified in the comp_data dictionary to get the path to the web directories." Arguments: env (dictionary) - environment dictionary comp_data (map) - component diag dir mapping """ for diag_type, key in comp_data.iteritems(): web_files = sorted(glob.glob('{0}/web_dirs/{1}.*'.format(env['PP_CASE_PATH'],key))) for web_file in web_files: if not web_file.endswith('~'): lines = [line.rstrip('\n') for line in open(web_file,'r')] for line in lines: values = line.split(':') if 'copied' not in values[-1].lower(): if values[-2] not in env.keys(): env[values[-2]] = [values[-1]] else: env[values[-2]].append(values[-1]) else: env[values[-2]] = [] if 'OCNDIAG_WEBDIR' not in env: env['OCNDIAG_WEBDIR'] = list() return env #======================================================================= # update_web_dirs - update the web_dirs with the copied status #======================================================================= def update_web_dirs(env, comp_data): """ update the $PP_CASE_PATH/web_dirs files specified in the comp_data dictionary to add the :copied string. Arguments: env (dictionary) - environment dictionary comp_data (map) - component diag dir mapping """ for diag_type, key in comp_data.iteritems(): web_files = sorted(glob.glob('{0}/web_dirs/{1}.*'.format(env['PP_CASE_PATH'],key))) for web_file in web_files: if not web_file.endswith('~'): newlines = list() lines = [line.rstrip('\n') for line in open(web_file,'r')] for line in lines: newline = line values = line.split(':') if 'copied' not in values[-1].lower(): newline = '{0}:copied'.format(line) newlines.append(newline) with open(web_file, 'w') as f: f.write('\n'.join(newlines) + '\n') f.close() return env #======================================================================= # copy_files - scp files from workdir to remote directory #======================================================================= def copy_files(env, comp, comp_data): """ copy html files from workdir to remote dir. Will prompt user if ssh keys are not set. Arguments: env (dictionary) - environment dictionary comp (string) - component comp_data (map) - component diag dir mapping """ remote = '{0}@{1}:{2}/{3}'.format(env['GLOBAL_WEBLOGIN'], env['GLOBAL_WEBHOST'], env['GLOBAL_REMOTE_WEBDIR'], comp) if comp != 'ocn': for diag_type, key in comp_data.iteritems(): # check if the diag_type key string that points to the local webdir is empty or not if key in env.keys(): for diag_dir in env[key]: if len(diag_dir) > 0: local = diag_dir if not os.path.isdir(local): print('copy_html WARNING: local directory = {0} does not exists.'.format(local)) else: # copy local to remote if not scp_files(env, local, remote): print('copy_html WARNING: unable to copy {0} to {1}'.format(local, remote)) print(' You will need to copy the files manually') else: # ocean need to create a tar file first for diag_dir in env['OCNDIAG_WEBDIR']: if os.path.isdir(diag_dir): ok_to_copy = True rootdir, workdir = os.path.split(diag_dir) # fix for when there is a / at the end of the path if len(workdir) == 0: rootdir, workdir = os.path.split(rootdir) # parse the workdir for years diag_parts = workdir.split('.')[-1].split('-') year0 = diag_parts[0] year1 = diag_parts[1] tarfile = 'ocn{0}-{1}.tar.gz'.format(year0, year1) cwd = os.getcwd() os.chdir(rootdir) if os.path.isfile(os.path.join(rootdir,tarfile)): print('copy_html WARNING: ocean tar file = {0} already exists - please delete first.'.format(os.path.join(rootdir,tarfile))) ok_to_copy = False else: tar_cmd = "tar cvfz {0} --exclude='*.nc' --exclude='*.nc_tmp' --exclude='*.tmp' --exclude='*.log.*' --exclude='*.asc' --exclude='*.ncl' --exclude='*.dt.*' {1}".format(tarfile, workdir) try: pipe = subprocess.Popen([tar_cmd], shell=True) pipe.wait() except Exception as e: print('copy_html WARNING: unable to execute tar command {0}'.format(tar_cmd)) print(' You will need to copy the files in {0} manually to a web server.'.format(diag_dir)) print(' {0} - {1}'.format(e.returncode, e.output)) ok_to_copy = False if ok_to_copy: if scp_files(env, tarfile, remote): # untar the file on remote server ok_to_remove = True try: pipe = subprocess.Popen(["ssh {0}@{1} 'cd {2}/{3} ; tar xvfz {4}'".format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp,tarfile)], shell=True) pipe.wait() except Exception as e: print('copy_html WARNING: unable to untar file {0} on remote server {1}@{2}:{3}/{4}'.format(tarfile, env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp)) print(' You will need to untar files manually') ok_to_remove = False if ok_to_remove: # remove the tar file on the remote server try: pipe = subprocess.Popen(["ssh {0}@{1} 'cd {2}/{3} ; rm {4}'".format(env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp,tarfile)], shell=True) pipe.wait() except Exception as e: print('copy_html WARNING: unable to remove tar file {0} on remote server {1}@{2}:{3}/{4}'.format(tarfile, env['GLOBAL_WEBLOGIN'],env['GLOBAL_WEBHOST'],env['GLOBAL_REMOTE_WEBDIR'],comp)) os.chdir(cwd) #======================================================================= # main #======================================================================= def main(options): """ main Arguments: options (list) - input options from command line """ env = dict() envFileList = list() compList = ['atm','ice','lnd','ocn'] activeList = list() # define diag dir mapping comp_lookup = {'atm' : {'model_vs_obs':'ATMDIAG_WEBDIR_MODEL_VS_OBS', 'model_vs_model':'ATMDIAG_WEBDIR_MODEL_VS_MODEL'}, 'ice' : {'model_vs_obs':'ICEDIAG_WEBDIR_MODEL_VS_OBS', 'model_vs_model':'ICEDIAG_WEBDIR_MODEL_VS_MODEL'}, 'lnd' : {'model_vs_obs':'LNDDIAG_WEBDIR_MODEL_VS_OBS', 'model_vs_model':'LNDDIAG_WEBDIR_MODEL_VS_MODEL'}, 'ocn' : {'model_webdir':'OCNDIAG_WEBDIR'}} # this script always run from the postprocessing caseroot pp_caseroot = os.getcwd() # initialize the env from the env*.xml files in the casedir envFileList.append('env_postprocess.xml') for comp in compList: envFile = 'env_diags_{0}.xml'.format(comp) envFileList.append(envFile) # load the env with all the env file entries env = cesmEnvLib.readXML(pp_caseroot, envFileList) # check if ssh key is set check_ssh_key(env, options.use_ssh_key) # copy the different diag component web files for comp in compList: key = 'GENERATE_DIAGS_{0}'.format(comp).upper() if env[key].upper() in ['T','TRUE'] : # create the toplevel remote directory if it doesn't already exist create_top_level(env, comp) comp_data = comp_lookup[comp] # read the web_dirs files to get the paths env = read_paths(env, comp_data) copy_files(env, comp, comp_data) activeList.append(comp) update_web_dirs(env, comp_data) #=================================== if __name__ == "__main__": options = commandline_options() try: status = main(options) sys.exit(status) except Exception as error: print(str(error)) if options.backtrace: traceback.print_exc() sys.exit(1)