Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 685 log updates #1992

Merged
merged 59 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
de77082
clean up to reduce number of indents to make code more readable
georgemccabe Dec 14, 2022
a945632
simplify logic for finding log filenames, remove unused function
georgemccabe Dec 14, 2022
11fa333
simplified logic to getting metplus log file and output appropriate l…
georgemccabe Dec 14, 2022
b8fb37f
remove print Running METplus version because the same info is logged …
georgemccabe Dec 14, 2022
d6f2177
change logs that are output to screen before log file is initialized …
georgemccabe Dec 14, 2022
4fb2fa2
added function to get useful message if log file is not set and call …
georgemccabe Dec 14, 2022
cd430ff
skip log message with logfile path if no log file is set
georgemccabe Dec 14, 2022
79c2f1d
log all environment variable values at DEBUG level
georgemccabe Dec 14, 2022
26d8c36
added support for setting LOG_LEVEL_TERMINAL to set log level for scr…
georgemccabe Dec 14, 2022
ea40378
change terminal log level to warning to limit screen output when a py…
georgemccabe Dec 14, 2022
9394f94
remove unnecessary extra variable, ci-run-all-cases
georgemccabe Dec 14, 2022
20e0728
change log level for automated tests from DEBUG to INFO to see how mu…
georgemccabe Dec 14, 2022
b31f69d
run GHA tests with log file and terminal level DEBUG, ci-run-all-cases
georgemccabe Dec 15, 2022
4ddf6ae
run GHA tests with log file and terminal level INFO, ci-run-all-cases
georgemccabe Dec 15, 2022
e7d5587
changed logs that list the length of time to run to INFO level, added…
georgemccabe Dec 15, 2022
b219ac5
turn on use case to test
georgemccabe Dec 15, 2022
83a5ddb
separate out setup commands from use case commands, only add status v…
georgemccabe Dec 15, 2022
901e332
create Docker container and run setup commands, then docker exec each…
georgemccabe Dec 15, 2022
e287614
add name identifier for container so exec and stop commands can find it
georgemccabe Dec 15, 2022
84d9d7d
docker run detached and run docker ps and images commands through run…
georgemccabe Dec 15, 2022
e390f16
start container in interactive mode detached, then exec commands to i…
georgemccabe Dec 15, 2022
e9b7377
do not exec detached because it will always return 0 error code, remo…
georgemccabe Dec 15, 2022
3815b0e
run docker exec commands with bash login shell (-l), try adding expor…
georgemccabe Dec 15, 2022
9b1b4af
run docker build command through docker command function
georgemccabe Dec 15, 2022
d72f792
added missing semicolon
georgemccabe Dec 15, 2022
bd6cc1e
force remove container at end of tests, ci-skip-unit-tests
georgemccabe Dec 15, 2022
9a453b6
fix adding export to bashrc, ci-skip-unit-tests
georgemccabe Dec 15, 2022
9817a1e
another test, ci-skip-unit-tests
georgemccabe Dec 15, 2022
8af5c65
comment out broken function
georgemccabe Dec 15, 2022
56c4999
try grouping GHA log output, ci-skip-unit-tests
georgemccabe Dec 15, 2022
7674b27
add GHA log group for each docker command
georgemccabe Dec 15, 2022
3505446
another attempt at setting path, ci-skip-unit-tests
georgemccabe Dec 15, 2022
ca5deff
end group before error message so it is visible at top level, fix cat…
georgemccabe Dec 15, 2022
7e96fb3
add any env var assignment to bashrc, changed setup_env items into a …
georgemccabe Dec 15, 2022
3a1ab23
remove source of bashrc file because running with login shell, added …
georgemccabe Dec 15, 2022
806f768
added GHA log groups for all commands run through time_command, moved…
georgemccabe Dec 16, 2022
34e1abd
fix spacing between functions
georgemccabe Dec 16, 2022
5fa6c63
clean up GHA log grouping, ci-skip-unit-tests
georgemccabe Dec 16, 2022
39983fb
put TIMING inside GHA log group and output error message outside grou…
georgemccabe Dec 16, 2022
5e6c4c5
turn off use case group after tests, ci-run-all-diff
georgemccabe Dec 16, 2022
f774bff
add GitHub Actions log group for full diff results so it is easier to…
georgemccabe Dec 16, 2022
39b8b8f
created python function to run commands with timing info, error repor…
georgemccabe Dec 16, 2022
69da329
call run_commands function to consistently run processes and group lo…
georgemccabe Dec 16, 2022
1baa8c0
changed calling of diff logic to start detached interactive container…
georgemccabe Dec 16, 2022
741bf67
turn on use case to test, ci-skip-unit-tests, ci-run-diff
georgemccabe Dec 16, 2022
3dd5b6a
fixed typo in import, ci-skip-unit-tests, ci-run-diff
georgemccabe Dec 16, 2022
c9a686c
turn off use case after test
georgemccabe Dec 16, 2022
22cb0ce
turn on use case group and add typo to conf to create example of how …
georgemccabe Dec 16, 2022
f7bebac
Revert "turn on use case group and add typo to conf to create example…
georgemccabe Dec 16, 2022
d80e8f4
clean up function call because run_commands now accepts a string inst…
georgemccabe Dec 16, 2022
e1fe6f6
added documentation for LOG_LEVEL_TERMINAL and cleaned up info about …
georgemccabe Dec 16, 2022
84767d0
if LOG_LEVEL_TERMINAL is set so that INFO log message are not output …
georgemccabe Dec 16, 2022
3bad7ef
set file log level to DEBUG for automated tests so that the screen ou…
georgemccabe Dec 16, 2022
64e82ad
fixed bug in automated logic for getting Dockerfile to run tests
georgemccabe Dec 16, 2022
205d460
gather list of use cases that failed or could not be run to print sum…
georgemccabe Dec 19, 2022
c679f3e
turn on use case group to test, ci-skip-unit-tests
georgemccabe Dec 19, 2022
b8722a0
purposely cause a use case to fail, ci-skip-unit-tests
georgemccabe Dec 19, 2022
c682a78
Revert "purposely cause a use case to fail, ci-skip-unit-tests"
georgemccabe Dec 19, 2022
e39cd0d
Revert "turn on use case group to test, ci-skip-unit-tests"
georgemccabe Dec 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/jobs/bash_functions.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
#! /bin/bash

# utility function to run command get log the time it took to run
# ::group:: and ::endgroup:: create collapsible log groups in GitHub Actions
function time_command {
local start_seconds=$SECONDS
echo "RUNNING: $*"
echo "::group::RUNNING: $*"
"$@"
local error=$?

local duration=$(( SECONDS - start_seconds ))
echo "TIMING: Command took `printf '%02d' $(($duration / 60))`:`printf '%02d' $(($duration % 60))` (MM:SS): '$*'"
echo "::endgroup::"

if [ ${error} -ne 0 ]; then
echo "ERROR: '$*' exited with status = ${error}"
fi

return $error
}
60 changes: 60 additions & 0 deletions .github/jobs/docker_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os
import re
import subprocess
import shlex
import time

# Utilities used by various CI jobs. Functionality includes:
# - Check if Docker data volumes need to be updated.
Expand All @@ -15,6 +18,7 @@
# extension to add to conda environments
VERSION_EXT = '.v5'


def get_data_repo(branch_name):
"""! Branch names that start with main_v or contain only
digits and dots with out without a prefix 'v' will return
Expand All @@ -26,10 +30,12 @@ def get_data_repo(branch_name):
return DOCKERHUB_METPLUS_DATA
return DOCKERHUB_METPLUS_DATA_DEV


def get_dockerhub_url(branch_name):
data_repo = get_data_repo(branch_name)
return f'https://hub.docker.com/v2/repositories/{data_repo}/tags'


def docker_get_volumes_last_updated(current_branch):
import requests
dockerhub_url = get_dockerhub_url(current_branch)
Expand Down Expand Up @@ -60,6 +66,7 @@ def docker_get_volumes_last_updated(current_branch):

return volumes_last_updated


def get_branch_name():
# get branch name from env var BRANCH_NAME
branch_name = os.environ.get('BRANCH_NAME')
Expand All @@ -82,3 +89,56 @@ def get_branch_name():
return None

return github_ref.replace('refs/heads/', '').replace('/', '_')


def run_commands(commands):
"""!Run a list of commands via subprocess. Print the command and the length
of time it took to run. Includes ::group:: and ::endgroup:: syntax which
creates log groups in GitHub Actions log output.

@param commands list of commands to run or a single command string
@returns True if all commands ran successfully, False if any commands fail
"""
# handle a single command string or list of command strings
if isinstance(commands, str):
command_list = [commands]
else:
command_list = commands

is_ok = True
for command in command_list:
error_message = None
print(f"::group::RUNNING {command}")
start_time = time.time()
try:
process = subprocess.Popen(shlex.split(command),
shell=False,
encoding='utf-8',
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
# Poll process.stdout to show stdout live
while True:
output = process.stdout.readline()
if process.poll() is not None:
break
if output:
print(output.strip())
rc = process.poll()
if rc:
raise subprocess.CalledProcessError(rc, command)

except subprocess.CalledProcessError as err:
error_message = f"ERROR: Command failed -- {err}"
is_ok = False

end_time = time.time()
print("TIMING: Command took "
f"{time.strftime('%M:%S', time.gmtime(end_time - start_time))}"
f" (MM:SS): '{command}')")

print("::endgroup::")

if error_message:
print(error_message)

return is_ok
6 changes: 3 additions & 3 deletions .github/jobs/get_data_volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from docker_utils import docker_get_volumes_last_updated, get_branch_name
from docker_utils import get_data_repo, DOCKERHUB_METPLUS_DATA_DEV
from docker_utils import run_commands


def main(args):
# get METplus version
Expand Down Expand Up @@ -94,9 +96,7 @@ def main(args):
print(f"CREATING DATA VOLUME FROM: {full_volume_name}")
cmd = (f'docker create --name {model_app_name} '
f'{full_volume_name}')
ret = subprocess.run(shlex.split(cmd))

if ret.returncode:
if not run_commands(cmd):
continue

# add name to volumes from list to pass to docker build
Expand Down
85 changes: 50 additions & 35 deletions .github/jobs/get_use_case_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ def handle_automation_env(host_name, reqs, work_dir):
conda_env_w_ext = f'{conda_env}{VERSION_EXT}'

# start building commands to run before run_metplus.py in Docker
setup_env = 'source /etc/bashrc;'
setup_env = []
setup_env.append(_add_to_bashrc('# BELOW WAS ADDED BY TEST SCRIPT'))

# add conda bin to beginning of PATH
python_dir = os.path.join('/usr', 'local', 'envs',
conda_env_w_ext, 'bin')
python_path = os.path.join(python_dir, 'python3')
setup_env += f' export PATH={python_dir}:$PATH;'
setup_env.append(_add_to_bashrc(f'export PATH={python_dir}:$PATH'))

# if py_embed listed in requirements and using a Python
# environment that differs from the MET env, set MET_PYTHON_EXE
Expand All @@ -86,45 +87,53 @@ def handle_automation_env(host_name, reqs, work_dir):
if any([item for item in PLOTCALC_KEYWORDS if item in str(reqs).lower()]):
ce_file = os.path.join(work_dir, '.github', 'parm',
f'Externals_metplotcalcpy{externals_ext}')
setup_env += (
f'cd {METPLUS_DOCKER_LOC};'
f'{work_dir}/manage_externals/checkout_externals -e {ce_file};'
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METplotpy;'
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METcalcpy;'
'cd -;'
)
setup_env.extend((
f'cd {METPLUS_DOCKER_LOC}',
f'{work_dir}/manage_externals/checkout_externals -e {ce_file}',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METplotpy',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METcalcpy',
'cd -',
))

# if metdataio is in requirements list, add command to obtain METdataio
if 'metdataio' in str(reqs).lower():
ce_file = os.path.join(work_dir, '.github', 'parm',
f'Externals_metdataio{externals_ext}')
setup_env += (
f'cd {METPLUS_DOCKER_LOC};'
f'{work_dir}/manage_externals/checkout_externals -e {ce_file};'
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METdataio;'
'cd -;'
)
setup_env.extend((
f'cd {METPLUS_DOCKER_LOC}',
f'{work_dir}/manage_externals/checkout_externals -e {ce_file}',
f'{python_path} -m pip install {METPLUS_DOCKER_LOC}/../METdataio',
'cd -',
))

# if gempak is in requirements list, add JRE bin to path for java
if 'gempak' in str(reqs).lower():
setup_env += 'export PATH=$PATH:/usr/lib/jvm/jre/bin;'
setup_env.append(_add_to_bashrc(
'export PATH=$PATH:/usr/lib/jvm/jre/bin'
))

# if metplus is in requirements list,
# add top of METplus repo to PYTHONPATH so metplus can be imported
if 'metplus' in str(reqs).lower():
setup_env += f'export PYTHONPATH={METPLUS_DOCKER_LOC}:$PYTHONPATH;'
setup_env.append(_add_to_bashrc(
f'export PYTHONPATH={METPLUS_DOCKER_LOC}:$PYTHONPATH'
))

# list packages in python environment that will be used
if conda_env not in NOT_PYTHON_ENVS:
setup_env += (
f'echo Using environment: dtcenter/metplus-envs:{conda_env_w_ext};'
f'echo cat /usr/local/envs/{conda_env_w_ext}/environments.yml;'
f'echo ----------------------------------------;'
f'cat /usr/local/envs/{conda_env_w_ext}/environments.yml;'
'echo ----------------------------------------;'
)
setup_env.extend((
f'echo Using environment: dtcenter/metplus-envs:{conda_env_w_ext}',
f'echo cat /usr/local/envs/{conda_env_w_ext}/environments.yml',
f'echo ----------------------------------------',
f'cat /usr/local/envs/{conda_env_w_ext}/environments.yml',
'echo ----------------------------------------',
))

return setup_env, py_embed_arg
return ';'.join(setup_env), py_embed_arg


def _add_to_bashrc(command):
return f"echo '{command};' >> /etc/bashrc"


def main(categories, subset_list, work_dir=None,
Expand All @@ -151,10 +160,13 @@ def main(categories, subset_list, work_dir=None,
for use_case_by_requirement in use_cases_by_req:
reqs = use_case_by_requirement.requirements

setup_env, py_embed_arg = handle_automation_env(host_name, reqs, work_dir)
setup_env, py_embed_arg = handle_automation_env(host_name, reqs,
work_dir)

# use status variable to track if any use cases failed
use_case_cmds = ['status=0']
use_case_cmds = []
if host_name != 'docker':
use_case_cmds.append('status=0')
for use_case in use_case_by_requirement.use_cases:
# add parm/use_cases path to config args if they are conf files
config_args = []
Expand All @@ -174,13 +186,14 @@ def main(categories, subset_list, work_dir=None,
use_case_cmds.append(use_case_cmd)
# check exit code from use case command and
# set status to non-zero value on error
use_case_cmds.append("if [ $? != 0 ]; then status=1; fi")
if host_name != 'docker':
use_case_cmds.append("if [ $? != 0 ]; then status=1; fi")

# if any use cases failed, force non-zero exit code with false
use_case_cmds.append("if [ $status != 0 ]; then false; fi")
if host_name != 'docker':
use_case_cmds.append("if [ $status != 0 ]; then false; fi")
# add commands to set up environment before use case commands
group_commands = f"{setup_env}{';'.join(use_case_cmds)}"
all_commands.append((group_commands, reqs))
all_commands.append((setup_env, use_case_cmds, reqs))

return all_commands

Expand All @@ -203,7 +216,6 @@ def handle_command_line_args():
else:
subset_list = None


# check if comparison flag should be set
if len(sys.argv) > 3:
do_comparison = True
Expand All @@ -216,7 +228,10 @@ def handle_command_line_args():
if __name__ == '__main__':
categories, subset_list, _ = handle_command_line_args()
all_commands = main(categories, subset_list)
for command, requirements in all_commands:
for setup_commands, use_case_commands, requirements in all_commands:
print(f"REQUIREMENTS: {','.join(requirements)}")
command_format = ';\\\n'.join(command.split(';'))
print(f"COMMAND:\n{command_format}\n")
if setup_commands:
command_format = ';\\\n'.join(setup_commands.split(';'))
print(f"SETUP COMMANDS:\n{command_format}\n")
command_format = ';\\\n'.join(use_case_commands)
print(f"USE CASE COMMANDS:\n{command_format}\n")
31 changes: 23 additions & 8 deletions .github/jobs/setup_and_run_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
sys.path.insert(0, ci_dir)

from jobs import get_data_volumes
from jobs.docker_utils import run_commands

CI_JOBS_DIR = '.github/jobs'

Expand Down Expand Up @@ -39,22 +40,32 @@

print(f"Output Volumes: {VOLUMES_FROM}")

volume_mounts = [
VOLUME_MOUNTS = [
f'-v {WS_PATH}:{GITHUB_WORKSPACE}',
f'-v {RUNNER_WORKSPACE}/output:/data/output',
f'-v {RUNNER_WORKSPACE}/diff:/data/diff',
]

mount_args = ' '.join(volume_mounts)
MOUNT_ARGS = ' '.join(VOLUME_MOUNTS)

# command to run inside Docker
cmd = (f'/usr/local/envs/diff{VERSION_EXT}/bin/python3 '
f'{GITHUB_WORKSPACE}/{CI_JOBS_DIR}/run_diff_docker.py')
diff_command = (f'/usr/local/envs/diff{VERSION_EXT}/bin/python3 '
f'{GITHUB_WORKSPACE}/{CI_JOBS_DIR}/run_diff_docker.py')

# start detached interactive diff env container
# mount METplus code and output dir, volumes from output volumes
docker_cmd = (
f'docker run -d --rm -it --name diff -e GITHUB_WORKSPACE {VOLUMES_FROM}'
f' {MOUNT_ARGS} dtcenter/metplus-envs:diff{VERSION_EXT} bash'
)
if not run_commands(docker_cmd):
sys.exit(1)

# run inside diff env: mount METplus code and output dir, volumes from output volumes
docker_cmd = (f'docker run -e GITHUB_WORKSPACE {VOLUMES_FROM} '
f'{mount_args} dtcenter/metplus-envs:diff{VERSION_EXT} '
f'bash -c "{cmd}"')
# execute command to run difference tests in Docker container
# do not run using run_commands function so GitHub Actions log grouping
# can be used to put full diff output into a group so it is easier to
# view the diff summary
docker_cmd = f'docker exec -e GITHUB_WORKSPACE diff bash -cl "{diff_command}"'
print(f'RUNNING: {docker_cmd}')
try:
process = subprocess.Popen(shlex.split(docker_cmd),
Expand All @@ -76,3 +87,7 @@
except subprocess.CalledProcessError as err:
print(f"ERROR: Command failed -- {err}")
sys.exit(1)

# force remove container to stop and remove it
if not run_commands('docker rm -f diff'):
sys.exit(1)
Loading