Support setting per-job-environment container resolvers using the container_resolvers key in the environment config.

This commit is contained in:
Nate Coraor
2021-08-17 10:28:51 -04:00
parent f71d982a38
commit a6ac841f85
9 changed files with 144 additions and 61 deletions
+2 -2
View File
@@ -1208,8 +1208,8 @@ class ConfiguresGalaxyMixin:
for enabled_container_type in self.container_finder._enabled_container_types(destination.params):
container_types_to_destinations[enabled_container_type].append(destination)
self.toolbox.dependency_manager.set_enabled_container_types(container_types_to_destinations)
self.toolbox.dependency_manager.resolver_classes.update(self.container_finder.container_registry.resolver_classes)
self.toolbox.dependency_manager.dependency_resolvers.extend(self.container_finder.container_registry.container_resolvers)
self.toolbox.dependency_manager.resolver_classes.update(self.container_finder.default_container_registry.resolver_classes)
self.toolbox.dependency_manager.dependency_resolvers.extend(self.container_finder.default_container_registry.container_resolvers)
def _configure_tool_data_tables(self, from_shed_config):
from galaxy.tools.data import ToolDataTableManager
+40 -14
View File
@@ -42,7 +42,9 @@ class ContainerFinder:
def __init__(self, app_info, mulled_resolution_cache=None):
self.app_info = app_info
self.container_registry = ContainerRegistry(app_info, mulled_resolution_cache=mulled_resolution_cache)
self.mulled_resolution_cache = mulled_resolution_cache
self.default_container_registry = ContainerRegistry(app_info, mulled_resolution_cache=mulled_resolution_cache)
self.destination_container_registeries = {}
def _enabled_container_types(self, destination_info):
return [t for t in ALL_CONTAINER_TYPES if self.__container_type_enabled(t, destination_info)]
@@ -51,11 +53,26 @@ class ContainerFinder:
"""Regardless of destination properties - find best container for tool.
Given container types and container.ToolInfo description of the tool."""
return self.container_registry.find_best_container_description(enabled_container_types, tool_info, **kwds)
return self.default_container_registry.find_best_container_description(enabled_container_types, tool_info, **kwds)
def resolve(self, enabled_container_types, tool_info, **kwds):
"""Regardless of destination properties - find ResolvedContainerDescription for tool."""
return self.container_registry.resolve(enabled_container_types, tool_info, **kwds)
return self.default_container_registry.resolve(enabled_container_types, tool_info, **kwds)
def _container_registry_for_destination(self, destination_info):
destination_id = destination_info.get("id") # Probably not the way to get the ID?
destination_container_registry = None
if destination_id and destination_id not in self.destination_container_registeries:
if 'container_resolvers' in destination_info:
destination_container_registry = ContainerRegistry(self.app_info, destination_info=destination_info, mulled_resolution_cache=self.mulled_resolution_cache)
self.destination_container_registeries[destination_id] = destination_container_registry
elif not destination_id and 'container_resolvers' in destination_info:
destination_container_registry = ContainerRegistry(self.app_info, destination_info=destination_info, mulled_resolution_cache=self.mulled_resolution_cache)
if destination_container_registry is None and destination_id and destination_id in self.destination_container_registeries:
destination_container_registry = self.destination_container_registeries[destination_id]
return destination_container_registry or self.default_container_registry
def find_container(self, tool_info, destination_info, job_info):
enabled_container_types = self._enabled_container_types(destination_info)
@@ -102,7 +119,8 @@ class ContainerFinder:
return container
# Otherwise lets see if we can find container for the tool.
container_description = self.find_best_container_description(enabled_container_types, tool_info)
container_registry = self._container_registry_for_destination(destination_info)
container_description = container_registry.find_best_container_description(enabled_container_types, tool_info)
container = __destination_container(container_description)
if container:
return container
@@ -124,7 +142,7 @@ class ContainerFinder:
return NULL_CONTAINER
def resolution_cache(self):
return self.container_registry.get_resolution_cache()
return self.default_container_registry.get_resolution_cache()
def __overridden_container_id(self, container_type, destination_info):
if not self.__container_type_enabled(container_type, destination_info):
@@ -186,25 +204,33 @@ class NullContainerFinder:
class ContainerRegistry:
"""Loop through enabled ContainerResolver plugins and find first match."""
def __init__(self, app_info, mulled_resolution_cache=None):
def __init__(self, app_info, destination_info=None, mulled_resolution_cache=None):
self.resolver_classes = self.__resolvers_dict()
self.enable_mulled_containers = app_info.enable_mulled_containers
self.app_info = app_info
self.container_resolvers = self.__build_container_resolvers(app_info)
self.container_resolvers = self.__build_container_resolvers(app_info, destination_info)
self.mulled_resolution_cache = mulled_resolution_cache
def __build_container_resolvers(self, app_info):
conf_file = getattr(app_info, 'container_resolvers_config_file', None)
conf_dict = getattr(app_info, 'container_resolvers_config_dict', None)
def __build_container_resolvers(self, app_info, destination_info):
app_conf_file = getattr(app_info, 'container_resolvers_config_file', None)
app_conf_dict = getattr(app_info, 'container_resolvers_config_dict', None)
if destination_info is not None:
conf_file = destination_info.get('container_resolvers_config_file', app_conf_file)
conf_dict = destination_info.get('container_resolvers', app_conf_dict)
else:
conf_file = app_conf_file
conf_dict = app_conf_dict
plugin_source = None
if conf_file and not os.path.exists(conf_file):
if conf_dict:
log.debug("Loading container resolution config inline from Galaxy or job configuration file")
plugin_source = plugin_config.plugin_source_from_dict(conf_dict)
elif conf_file and not os.path.exists(conf_file):
log.warning(f"Unable to find config file '{conf_file}'")
elif conf_file:
log.debug("Loading container resolution config from file '{conf_file}'")
plugin_source = plugin_config.plugin_source_from_path(conf_file)
elif conf_dict:
log.debug("Loading container resolution config inline from Galaxy configuration file")
plugin_source = plugin_config.plugin_source_from_dict(conf_dict)
if plugin_source:
return self._parse_resolver_conf(plugin_source)
return self.__default_container_resolvers()
@@ -0,0 +1,25 @@
<tool id="mulled_example_broken_no_requirements_fallback" name="mulled_example_broken_no_requirements_fallback" version="0.1.0">
<!-- mulled_example_explicit without requirements... to test mapping with job conf inline container resolvers config -->
<!--
<requirements>
<requirement type="package" version="0.7.15">bwa</requirement>
<container type="docker">quay.io/biocontainers/bwa:0.7.15-\-0</container>
</requirements>
-->
<stdio>
<exit_code range="2:" />
</stdio>
<command><![CDATA[
bwa > $output_1 2>&1
]]></command>
<inputs>
</inputs>
<outputs>
<data name="output_1" />
</outputs>
<tests>
</tests>
<help><![CDATA[
TODO: Fill in help.
]]></help>
</tool>
@@ -243,6 +243,7 @@
<tool file="mulled_example_explicit.xml" />
<tool file="mulled_example_invalid_case.xml" />
<tool file="mulled_example_broken_no_requirements.xml" />
<tool file="mulled_example_broken_no_requirements_fallback.xml" />
<tool file="simple_constructs.yml" />
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0"?>
<job_conf>
<plugins>
<plugin id="local" type="runner" load="galaxy.jobs.runners.local:LocalJobRunner" workers="1"/>
</plugins>
<destinations default="local_docker">
<destination id="local_docker" runner="local">
<param id="docker_enabled">true</param>
<param id="docker_sudo">false</param>
</destination>
<destination id="local_upload" runner="local">
</destination>
</destinations>
<tools>
<tool id="upload1" destination="local_upload" />
</tools>
</job_conf>
+26
View File
@@ -0,0 +1,26 @@
---
runners:
local:
load: galaxy.jobs.runners.local:LocalJobRunner
workers: 1
execution:
default: local_docker
environments:
local_docker:
runner: local
docker_enabled: true
local_docker_inline_container_resolvers:
runner: local
docker_enabled: true
container_resolvers:
- type: fallback
identifier: 'quay.io/biocontainers/bwa:0.7.15--0'
local_upload:
runner: local
tools:
- id: upload1
environment: local_upload
- id: mulled_example_broken_no_requirements_fallback
environment: local_docker_inline_container_resolvers
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0"?>
<job_conf>
<plugins>
<plugin id="local" type="runner" load="galaxy.jobs.runners.local:LocalJobRunner" workers="1"/>
</plugins>
<destinations default="local_singularity">
<destination id="local_singularity" runner="local">
<param id="singularity_enabled">true</param>
</destination>
<destination id="local_upload" runner="local">
</destination>
</destinations>
<tools>
<tool class="local" destination="local_upload" />
</tools>
</job_conf>
+18
View File
@@ -0,0 +1,18 @@
---
runners:
local:
load: galaxy.jobs.runners.local:LocalJobRunner
workers: 1
execution:
default: local_singularity
environments:
local_singularity:
runner: local
singularity_enabled: true
local_upload:
runner: local
tools:
- id: upload1
environment: local_upload
+32 -2
View File
@@ -12,8 +12,8 @@ from galaxy_test.driver import integration_util
from .test_job_environments import RunsEnvironmentJobs
SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
DOCKERIZED_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "dockerized_job_conf.xml")
SINGULARITY_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "singularity_job_conf.xml")
DOCKERIZED_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "dockerized_job_conf.yml")
SINGULARITY_JOB_CONFIG_FILE = os.path.join(SCRIPT_DIRECTORY, "singularity_job_conf.yml")
EXTENDED_TIMEOUT = 120
@@ -202,6 +202,36 @@ class InlineContainerConfigurationTestCase(MappingContainerResolverTestCase):
config["container_resolvers"] = container_resolvers_config
class InlineJobEnvironmentContainerResolverTestCase(integration_util.IntegrationTestCase):
framework_tool_and_types = True
container_type = 'docker'
job_config_file = DOCKERIZED_JOB_CONFIG_FILE
@classmethod
def handle_galaxy_config_kwds(cls, config):
cls.jobs_directory = cls._test_driver.mkdtemp()
config["jobs_directory"] = cls.jobs_directory
config["job_config_file"] = cls.job_config_file
disable_dependency_resolution(config)
@classmethod
def setUpClass(cls):
skip_if_container_type_unavailable(cls)
super().setUpClass()
def setUp(self):
super().setUp()
self.dataset_populator = DatasetPopulator(self.galaxy_interactor)
self.history_id = self.dataset_populator.new_history()
def test_inline_environment_container_resolver_configuration(self):
self.dataset_populator.run_tool("mulled_example_broken_no_requirements_fallback", {}, self.history_id)
self.dataset_populator.wait_for_history(self.history_id, assert_ok=True)
output = self.dataset_populator.get_history_dataset_content(self.history_id, timeout=EXTENDED_TIMEOUT)
assert "0.7.15-r1140" in output
# Singularity 2.4 in the official Vagrant issue has some problems running this test
# case by default because subdirectories of /tmp don't bind correctly. Overridding
# TMPDIR can fix this.