mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Home Directory Handling ----------------------- - For profile < 18.01 tools: - If use_shared_home="false" is set on the command block - the tool will be given a clean $HOME directory. - If ``shared_home_dir`` is set in Galaxy's config or the job destination configuration, Galaxy will set $HOME in the tool's job environment to point at this directory. - For profile >= 18.01 tools, jobs will be given a clean home directory by default unless ``use_shared_home="true"`` is set. If that is set, the profile < 18.01 behavior is used. In addition to these changes for Galaxy tools, the tool framework itself has been updated to allow other potential behaviors including the CWL defaults. Integration tests for each of these cases has been added. Upgrading to 18.01 - we recommend dropping overriding ``HOME`` in any Galaxy environment configuration (e.g. env directives in job_conf.xml). If any such configuration is found, we recommend instead setting "shared_home_dir" for destinations to the that cluster destination's shared HOME directory. If all destinations share a single HOME directory, this can be set in galaxy.ini instead of job_conf.xml. Temp Directory Handling ----------------------- There were serious disagreements with how to proceed here. I felt we should aggressively isolate tool TMP directories and provide deployers options to tweak these through structured arguments. Nicola felt we should defer to the environment variables already being set job_conf.xml - but tweak their meanings by default. Nate fell somewhere in between. I think all sides have merit and could make sense depending on what you want to improve (easing cognative load, reducing out-of-box errors, easing advanced deployer configs, structured reasonsing of paths by Galaxy, etc...). I've navigated a path here that doesn't particularly reduce out of the box errors as I wanted by setting up a per-job temp directory by default but does make it possible and shouldn't break any existing configurations and doesn't make it anything more difficult to manage these things via environment variables. The new approach doesn't change any thing by default for Galaxy tools (regardless of tool profile version). The only way to achieve new behaviors is for the deployer to set a job_conf parameter called "tmp_dir". If this is set, it can be set to "True" (to default to a new, clean directory below the job directory) or to a shell expression to allow setting this dynamically at runtime to paths or paths beneath directories as needed on a per-destination basis. If a temp directory is set this way all three variables TMP, TMPDIR, and TEMP are set. Docker Environment Handling --------------------------- - Mirror the CWL behavior of mounting an external /tmp by default. - For newer tools, pass through the HOME, TMP, TMPDIR, and TEMP environment variables into the Docker container in addition to GALAXY_SLOTS. - Fix the pass through of environment variables into Docker containers.
220 lines
7.2 KiB
Python
220 lines
7.2 KiB
Python
import os
|
|
import threading
|
|
import time
|
|
from unittest import TestCase
|
|
|
|
import psutil
|
|
|
|
from galaxy import model
|
|
from galaxy.jobs import metrics
|
|
from galaxy.jobs.runners import local
|
|
from galaxy.util import bunch
|
|
from ..tools_support import (
|
|
UsesApp,
|
|
UsesTools
|
|
)
|
|
|
|
|
|
class TestLocalJobRunner(TestCase, UsesApp, UsesTools):
|
|
|
|
def setUp(self):
|
|
self.setup_app()
|
|
self._init_tool()
|
|
self.app.job_metrics = metrics.JobMetrics()
|
|
self.job_wrapper = MockJobWrapper(self.app, self.test_directory, self.tool)
|
|
|
|
def tearDown(self):
|
|
self.tear_down_app()
|
|
|
|
def test_run(self):
|
|
self.job_wrapper.command_line = "echo HelloWorld"
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert self.job_wrapper.stdout.strip() == "HelloWorld"
|
|
|
|
def test_galaxy_lib_on_path(self):
|
|
self.job_wrapper.command_line = '''python -c "import galaxy.util"'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert self.job_wrapper.exit_code == 0
|
|
|
|
def test_default_slots(self):
|
|
self.job_wrapper.command_line = '''echo $GALAXY_SLOTS'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert self.job_wrapper.stdout.strip() == "1"
|
|
|
|
def test_slots_override(self):
|
|
# Set local_slots in job destination to specify slots for
|
|
# local job runner.
|
|
self.job_wrapper.job_destination.params["local_slots"] = 3
|
|
self.job_wrapper.command_line = '''echo $GALAXY_SLOTS'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert self.job_wrapper.stdout.strip() == "3"
|
|
|
|
def test_exit_code(self):
|
|
self.job_wrapper.command_line = '''sh -c "exit 4"'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert self.job_wrapper.exit_code == 4
|
|
|
|
def test_metadata_gets_set(self):
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert os.path.exists(self.job_wrapper.mock_metadata_path)
|
|
|
|
def test_metadata_gets_set_if_embedded(self):
|
|
self.job_wrapper.job_destination.params["embed_metadata_in_job"] = "True"
|
|
|
|
# Kill off cruft for _handle_metadata_externally and make sure job stil works...
|
|
self.job_wrapper.external_output_metadata = None
|
|
self.app.datatypes_registry.set_external_metadata_tool = None
|
|
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.queue_job(self.job_wrapper)
|
|
assert os.path.exists(self.job_wrapper.mock_metadata_path)
|
|
|
|
def test_stopping_job(self):
|
|
self.job_wrapper.command_line = '''python -c "import time; time.sleep(15)"'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
|
|
def queue():
|
|
runner.queue_job(self.job_wrapper)
|
|
|
|
t = threading.Thread(target=queue)
|
|
t.start()
|
|
external_id = self.job_wrapper.wait_for_external_id()
|
|
mock_job = bunch.Bunch(
|
|
get_external_output_metadata=lambda: None,
|
|
get_job_runner_external_id=lambda: str(external_id),
|
|
get_id=lambda: 1
|
|
)
|
|
assert psutil.pid_exists(external_id)
|
|
runner.stop_job(mock_job)
|
|
t.join(1)
|
|
assert not psutil.pid_exists(external_id)
|
|
|
|
def test_shutdown_no_jobs(self):
|
|
self.app.config.monitor_thread_join_timeout = 5
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
runner.shutdown()
|
|
|
|
def test_stopping_job_at_shutdown(self):
|
|
self.job_wrapper.command_line = '''python -c "import time; time.sleep(15)"'''
|
|
runner = local.LocalJobRunner(self.app, 1)
|
|
self.app.config.monitor_thread_join_timeout = 15
|
|
|
|
def queue():
|
|
runner.queue_job(self.job_wrapper)
|
|
|
|
t = threading.Thread(target=queue)
|
|
t.start()
|
|
external_id = self.job_wrapper.wait_for_external_id()
|
|
assert psutil.pid_exists(external_id)
|
|
runner.shutdown()
|
|
t.join(1)
|
|
assert not psutil.pid_exists(external_id)
|
|
assert "job terminated by Galaxy shutdown" in self.job_wrapper.fail_message
|
|
|
|
|
|
class MockJobWrapper(object):
|
|
|
|
def __init__(self, app, test_directory, tool):
|
|
working_directory = os.path.join(test_directory, "workdir")
|
|
tool_working_directory = os.path.join(working_directory, "working")
|
|
os.makedirs(tool_working_directory)
|
|
self.app = app
|
|
self.tool = tool
|
|
self.requires_containerization = False
|
|
self.state = model.Job.states.QUEUED
|
|
self.command_line = "echo HelloWorld"
|
|
self.environment_variables = []
|
|
self.commands_in_new_shell = False
|
|
self.prepare_called = False
|
|
self.write_version_cmd = None
|
|
self.dependency_shell_commands = None
|
|
self.working_directory = working_directory
|
|
self.tool_working_directory = tool_working_directory
|
|
self.requires_setting_metadata = True
|
|
self.job_destination = bunch.Bunch(id="default", params={})
|
|
self.galaxy_lib_dir = os.path.abspath("lib")
|
|
self.job_id = 1
|
|
self.external_id = None
|
|
self.output_paths = ['/tmp/output1.dat']
|
|
self.mock_metadata_path = os.path.abspath(os.path.join(test_directory, "METADATA_SET"))
|
|
self.metadata_command = "touch %s" % self.mock_metadata_path
|
|
self.galaxy_virtual_env = None
|
|
self.shell = "/bin/bash"
|
|
self.cleanup_job = "never"
|
|
self.tmp_dir_creation_statement = ""
|
|
|
|
# Cruft for setting metadata externally, axe at some point.
|
|
self.external_output_metadata = bunch.Bunch(
|
|
set_job_runner_external_pid=lambda pid, session: None
|
|
)
|
|
self.app.datatypes_registry.set_external_metadata_tool = bunch.Bunch(
|
|
build_dependency_shell_commands=lambda: []
|
|
)
|
|
|
|
def check_tool_output(*args, **kwds):
|
|
return "ok"
|
|
|
|
def wait_for_external_id(self):
|
|
"""Test method for waiting til an external id has been registered."""
|
|
external_id = None
|
|
for i in range(50):
|
|
external_id = self.external_id
|
|
if external_id:
|
|
break
|
|
time.sleep(.1)
|
|
return external_id
|
|
|
|
def prepare(self):
|
|
self.prepare_called = True
|
|
|
|
def set_job_destination(self, job_destination, external_id):
|
|
self.external_id = external_id
|
|
|
|
def get_command_line(self):
|
|
return self.command_line
|
|
|
|
def get_id_tag(self):
|
|
return "1"
|
|
|
|
def get_state(self):
|
|
return self.state
|
|
|
|
def change_state(self, state):
|
|
self.state = state
|
|
|
|
def get_output_fnames(self):
|
|
return []
|
|
|
|
def get_job(self):
|
|
return model.Job()
|
|
|
|
def setup_external_metadata(self, **kwds):
|
|
return self.metadata_command
|
|
|
|
def get_env_setup_clause(self):
|
|
return ""
|
|
|
|
def has_limits(self):
|
|
return False
|
|
|
|
def fail(self, message, exception):
|
|
self.fail_message = message
|
|
self.fail_exception = exception
|
|
|
|
def finish(self, stdout, stderr, exit_code, **kwds):
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
self.exit_code = exit_code
|
|
|
|
def tmp_directory(self):
|
|
return None
|
|
|
|
def home_directory(self):
|
|
return None
|