diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index 88799042671..2e4a325377f 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -217,6 +217,18 @@ paste.app_factory = galaxy.web.buildapp:app_factory # of extra disk space usage and extra time spent copying packages. #conda_copy_dependencies = False +# Certain dependency resolvers (namely Conda) take a considerable amount of +# time to build an isolated job environment in the job_working_directory if the +# job working directory is on a network share. Set the following option to True +# to cache the dependencies in a folder. This option is beta and should only be +# used if you experience long waiting times before a job is actually submitted +# to your cluster. +#use_cached_dependency_manager = False + +# By default the tool_dependency_cache_dir is the _cache directory +# of the tool dependency directory +#tool_dependency_cache_dir = /_cache + # File containing the Galaxy Tool Sheds that should be made available to # install from in the admin interface (.sample used if default does not exist). #tool_sheds_config_file = config/tool_sheds_conf.xml diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index be422486e5d..49f49bd3c36 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -325,6 +325,8 @@ class Configuration( object ): else: self.tool_dependency_dir = None self.use_tool_dependencies = os.path.exists(self.dependency_resolvers_config_file) + self.use_cached_dependency_manager = string_as_bool(kwargs.get("use_cached_dependency_manager", 'False')) + self.tool_dependency_cache_dir = kwargs.get( 'tool_dependency_cache_dir', os.path.join(self.tool_dependency_dir, '_cache')) self.enable_beta_mulled_containers = string_as_bool( kwargs.get( 'enable_beta_mulled_containers', 'False' ) ) containers_resolvers_config_file = kwargs.get( 'containers_resolvers_config_file', None ) diff --git a/lib/galaxy/datatypes/converters/fasta_to_2bit.xml b/lib/galaxy/datatypes/converters/fasta_to_2bit.xml index a8e3bb3204f..3ccd7ab7db8 100644 --- a/lib/galaxy/datatypes/converters/fasta_to_2bit.xml +++ b/lib/galaxy/datatypes/converters/fasta_to_2bit.xml @@ -2,6 +2,7 @@ + ucsc-fatotwobit ucsc_tools faToTwoBit '$input' '$output' diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index f11d1d5d5c3..ba8ee6ce1a2 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -34,6 +34,7 @@ from galaxy.tools.actions.data_source import DataSourceToolAction from galaxy.tools.actions.data_manager import DataManagerToolAction from galaxy.tools.actions.model_operations import ModelOperationToolAction from galaxy.tools.deps import views +from galaxy.tools.deps import CachedDependencyManager from galaxy.tools.parameters import params_to_incoming, check_param, params_from_strings, params_to_strings, visit_input_values from galaxy.tools.parameters import output_collect from galaxy.tools.parameters.basic import (BaseURLToolParameter, @@ -1305,17 +1306,30 @@ class Tool( object, Dictifiable ): visit_input_values( self.inputs, values, validate_inputs ) return messages + def build_dependency_cache(self, **kwds): + if isinstance(self.app.toolbox.dependency_manager, CachedDependencyManager): + self.app.toolbox.dependency_manager.build_cache( + requirements=self.requirements, + installed_tool_dependencies=self.installed_tool_dependencies, + tool_dir=self.tool_dir, + job_directory=None, + metadata=False, + tool_instance=self, + **kwds + ) + def build_dependency_shell_commands( self, job_directory=None, metadata=False ): - """Return a list of commands to be run to populate the current environment to include this tools requirements.""" - requirements_to_dependencies = self.app.toolbox.dependency_manager.requirements_to_dependencies( - self.requirements, + """ + Return a list of commands to be run to populate the current environment to include this tools requirements. + """ + return self.app.toolbox.dependency_manager.dependency_shell_commands( + requirements=self.requirements, installed_tool_dependencies=self.installed_tool_dependencies, tool_dir=self.tool_dir, job_directory=job_directory, metadata=metadata, + tool_instance=self ) - self.dependencies = [dep.to_dict() for dep in requirements_to_dependencies.values()] - return [dep.shell_commands(req) for req, dep in requirements_to_dependencies.items()] @property def installed_tool_dependencies(self): diff --git a/lib/galaxy/tools/deps/__init__.py b/lib/galaxy/tools/deps/__init__.py index 4ec3e42a1a4..640726f73d9 100644 --- a/lib/galaxy/tools/deps/__init__.py +++ b/lib/galaxy/tools/deps/__init__.py @@ -2,12 +2,17 @@ Dependency management for tools. """ +import json import logging import os.path +import shutil from collections import OrderedDict -from galaxy.util import plugin_config +from galaxy.util import ( + hash_util, + plugin_config +) from .resolvers import NullDependency from .resolvers.conda import CondaDependencyResolver, DEFAULT_ENSURE_CHANNELS @@ -45,7 +50,11 @@ def build_dependency_manager( config ): if value is CONFIG_VAL_NOT_FOUND: value = default_value dependency_manager_kwds[key] = value - dependency_manager = DependencyManager( **dependency_manager_kwds ) + if config.use_cached_dependency_manager: + dependency_manager_kwds['tool_dependency_cache_dir'] = config.tool_dependency_cache_dir + dependency_manager = CachedDependencyManager(**dependency_manager_kwds) + else: + dependency_manager = DependencyManager( **dependency_manager_kwds ) else: dependency_manager = NullDependencyManager() @@ -109,6 +118,8 @@ class DependencyManager( object ): log.debug(dependency.resolver_msg) if dependency.dependency_type: requirement_to_dependency[requirement] = dependency + if 'tool_instance' in kwds: + kwds['tool_instance'].dependencies = [dep.to_dict() for dep in requirement_to_dependency.values()] return requirement_to_dependency def uses_tool_shed_dependencies(self): @@ -155,3 +166,50 @@ class DependencyManager( object ): def __resolvers_dict( self ): import galaxy.tools.deps.resolvers return plugin_config.plugins_dict( galaxy.tools.deps.resolvers, 'resolver_type' ) + + +class CachedDependencyManager(DependencyManager): + def __init__(self, default_base_path, conf_file=None, **extra_config): + super(CachedDependencyManager, self).__init__(default_base_path=default_base_path, conf_file=conf_file, **extra_config) + + def build_cache(self, requirements, **kwds): + resolved_dependencies = self.requirements_to_dependencies(requirements, **kwds) + cacheable_dependencies = [dep for req, dep in resolved_dependencies.items() if dep.cacheable] + hashed_requirements_dir = self.get_hashed_requirements_path(cacheable_dependencies) + if kwds.get('force_rebuild', False) and os.path.exists(hashed_requirements_dir): + try: + shutil.rmtree(hashed_requirements_dir) + except Exception: + log.warning("Could not delete cached requirements directory '%s'" % hashed_requirements_dir) + pass + [dep.build_cache(hashed_requirements_dir) for dep in cacheable_dependencies] + + def dependency_shell_commands( self, requirements, **kwds ): + """ + Runs a set of requirements through the dependency resolvers and returns + a list of commands required to activate the dependencies. If dependencies + are cacheable and the cache exists, will generate commands to activate + cached environments. + """ + resolved_dependencies = self.requirements_to_dependencies(requirements, **kwds) + cacheable_dependencies = [dep for req, dep in resolved_dependencies.items() if dep.cacheable] + hashed_requirements_dir = self.get_hashed_requirements_path(cacheable_dependencies) + if os.path.exists(hashed_requirements_dir): + [dep.set_cache_path(hashed_requirements_dir) for dep in cacheable_dependencies] + commands = [dep.shell_commands(req) for req, dep in resolved_dependencies.items()] + return commands + + def hash_requirements(self, resolved_dependencies): + """Return hash for requirements""" + resolved_dependencies = [[(dep.name, dep.version, dep.exact, dep.dependency_type) for dep in resolved_dependencies]] + hash_str = json.dumps(sorted([resolved_dependencies])) + return hash_util.new_secure_hash(hash_str)[:8] # short hash + + def get_hashed_requirements_path(self, resolved_dependencies): + """ + Returns the path to the hashed requirements directory (but does not evaluate whether the path exists) + :param requirements: + :return: + """ + req_hashes = self.hash_requirements(resolved_dependencies) + return os.path.join(self.extra_config['tool_dependency_cache_dir'], req_hashes) diff --git a/lib/galaxy/tools/deps/conda_util.py b/lib/galaxy/tools/deps/conda_util.py index 9914fdd016f..47f1b090006 100644 --- a/lib/galaxy/tools/deps/conda_util.py +++ b/lib/galaxy/tools/deps/conda_util.py @@ -54,7 +54,7 @@ class CondaContext(installable.InstallableContext): def __init__(self, conda_prefix=None, conda_exec=None, shell_exec=None, debug=False, ensure_channels='', - condarc_override=None, use_path_exec=USE_PATH_EXEC_DEFAULT): + condarc_override=None, use_path_exec=USE_PATH_EXEC_DEFAULT, copy_dependencies=False): self.condarc_override = condarc_override if not conda_exec and use_path_exec: conda_exec = commands.which("conda") @@ -63,6 +63,7 @@ class CondaContext(installable.InstallableContext): self.conda_exec = conda_exec self.debug = debug self.shell_exec = shell_exec or commands.shell + self.copy_dependencies = copy_dependencies if conda_prefix is None: info = self.conda_info() @@ -213,6 +214,17 @@ class CondaContext(installable.InstallableContext): install_base_args.extend(args) return self.exec_command("install", install_base_args) + def exec_clean(self, args=[]): + """ + Clean up after conda installation. + """ + clean_base_args = [ + "--tarballs", + "-y" + ] + clean_base_args.extend(args) + return self.exec_command("clean", clean_base_args) + def export_list(self, name, path): return self.exec_command("list", [ "--name", name, @@ -487,6 +499,7 @@ def build_isolated_environment( return (path or tempdir_name, exit_code) finally: + conda_context.exec_clean() shutil.rmtree(tempdir) diff --git a/lib/galaxy/tools/deps/requirements.py b/lib/galaxy/tools/deps/requirements.py index 2b209f63362..0414ea2526a 100644 --- a/lib/galaxy/tools/deps/requirements.py +++ b/lib/galaxy/tools/deps/requirements.py @@ -25,6 +25,9 @@ class ToolRequirement( object ): type = dict.get("type", None) return ToolRequirement( name=name, type=type, version=version ) + def __eq__(self, other): + return self.name == other.name and self.type == other.type and self.version == other.version + DEFAULT_CONTAINER_TYPE = "docker" DEFAULT_CONTAINER_RESOLVE_DEPENDENCIES = False diff --git a/lib/galaxy/tools/deps/resolvers/__init__.py b/lib/galaxy/tools/deps/resolvers/__init__.py index 6165fdb6af8..c24c107887a 100644 --- a/lib/galaxy/tools/deps/resolvers/__init__.py +++ b/lib/galaxy/tools/deps/resolvers/__init__.py @@ -81,8 +81,9 @@ class InstallableDependencyResolver: class Dependency(Dictifiable, object): - dict_collection_visible_keys = ['dependency_type', 'exact', 'name', 'version'] + dict_collection_visible_keys = ['dependency_type', 'exact', 'name', 'version', 'cacheable'] __metaclass__ = ABCMeta + cacheable = False @abstractmethod def shell_commands( self, requirement ): @@ -121,3 +122,7 @@ class NullDependency( Dependency ): def shell_commands( self, requirement ): return None + + +class DependencyException(Exception): + pass diff --git a/lib/galaxy/tools/deps/resolvers/conda.py b/lib/galaxy/tools/deps/resolvers/conda.py index 2a1c13f5116..a21a37ebd4a 100644 --- a/lib/galaxy/tools/deps/resolvers/conda.py +++ b/lib/galaxy/tools/deps/resolvers/conda.py @@ -21,6 +21,7 @@ from ..conda_util import ( ) from ..resolvers import ( Dependency, + DependencyException, DependencyResolver, InstallableDependencyResolver, ListableDependencyResolver, @@ -66,6 +67,7 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In dependency_manager.default_base_path, DEFAULT_CONDARC_OVERRIDE ) + copy_dependencies = _string_as_bool(get_option("copy_dependencies")) conda_exec = get_option("exec") debug = _string_as_bool(get_option("debug")) ensure_channels = get_option("ensure_channels") @@ -84,18 +86,21 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In ensure_channels=ensure_channels, condarc_override=condarc_override, use_path_exec=use_path_exec, + copy_dependencies=copy_dependencies ) self.ensure_channels = ensure_channels # Conda operations options (these define how resolution will occur) auto_install = _string_as_bool(get_option("auto_install")) - copy_dependencies = _string_as_bool(get_option("copy_dependencies")) self.auto_init = _string_as_bool(get_option("auto_init")) self.conda_context = conda_context self.disabled = not galaxy.tools.deps.installable.ensure_installed(conda_context, install_conda, self.auto_init) self.auto_install = auto_install self.copy_dependencies = copy_dependencies + def clean(self, **kwds): + return self.conda_context.exec_clean() + def resolve(self, name, version, type, **kwds): # Check for conda just not being there, this way we can enable # conda by default and just do nothing in not configured. @@ -115,20 +120,7 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In ) job_directory = kwds.get("job_directory", None) - if job_directory is None: # Job directory is None when resolve() called by find_dep() - if is_installed: - return CondaDependency( - False, - os.path.join(self.conda_context.envs_path, conda_target.install_environment), - exact, - name=name, - version=version - ) - else: - log.warning("Conda dependency resolver not sent job directory.") - return NullDependency(version=version, name=name) - - if not is_installed and self.auto_install: + if not is_installed and self.auto_install and job_directory: is_installed = self.install_dependency(name=name, version=version, type=type) if not is_installed: @@ -140,23 +132,19 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In conda_env = "conda-metadata-env" else: conda_env = "conda-env" - conda_environment = os.path.join(job_directory, conda_env) - env_path, exit_code = build_isolated_environment( - conda_target, - path=conda_environment, - copy=self.copy_dependencies, - conda_context=self.conda_context, - ) - if not exit_code: - return CondaDependency( - self.conda_context.activate, - conda_environment, - exact, - name, - version - ) + + if job_directory: + conda_environment = os.path.join(job_directory, conda_env) else: - return NullDependency(version=version, name=name) + conda_environment = None + + return CondaDependency( + self.conda_context, + conda_environment, + exact, + name, + version + ) def list_dependencies(self): for install_target in installed_conda_targets(self.conda_context): @@ -204,13 +192,16 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In class CondaDependency(Dependency): dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['environment_path', 'name', 'version'] dependency_type = 'conda' + cacheable = True - def __init__(self, activate, environment_path, exact, name=None, version=None): - self.activate = activate + def __init__(self, conda_context, environment_path, exact, name=None, version=None): + self.activate = conda_context.activate + self.conda_context = conda_context self.environment_path = environment_path self._exact = exact self._name = name self._version = version + self.cache_path = None @property def exact(self): @@ -224,8 +215,34 @@ class CondaDependency(Dependency): def version(self): return self._version + def build_cache(self, cache_path): + self.set_cache_path(cache_path) + self.build_environment() + + def set_cache_path(self, cache_path): + self.cache_path = cache_path + self.environment_path = cache_path + + def build_environment(self): + env_path, exit_code = build_isolated_environment( + CondaTarget(self.name, self.version), + path=self.environment_path, + copy=self.conda_context.copy_dependencies, + conda_context=self.conda_context, + ) + if exit_code: + if len(os.path.abspath(self.environment_path)) > 79: + # TODO: remove this once conda_build version 2 is released and packages have been rebuilt. + raise DependencyException("Conda dependency failed to build job environment. " + "This is most likely a limitation in conda. " + "You can try to shorten the path to the job_working_directory.") + raise DependencyException("Conda dependency seemingly installed but failed to build job environment.") + def shell_commands(self, requirement): - return """[ "$CONDA_DEFAULT_ENV" = "%s" ] || . %s '%s' 2>&1 """ % ( + if not self.cache_path: + # Build an isolated environment if not using a cached dependency manager + self.build_environment() + return """[ "$CONDA_DEFAULT_ENV" = "%s" ] || . %s '%s' > conda_activate.log 2>&1 """ % ( self.environment_path, self.activate, self.environment_path diff --git a/lib/galaxy/tools/deps/views.py b/lib/galaxy/tools/deps/views.py index be4be6458b3..8f23335d7df 100644 --- a/lib/galaxy/tools/deps/views.py +++ b/lib/galaxy/tools/deps/views.py @@ -127,3 +127,15 @@ class DependencyResolversView(object): def get_requirements_status(self, requested_requirements, installed_tool_dependencies=None): return [self.manager_dependency(installed_tool_dependencies=installed_tool_dependencies, **req) for req in requested_requirements] + + def clean(self, index=None, **kwds): + if index: + resolver = self._dependency_resolver(index) + if not hasattr(resolver, "clean"): + raise NotImplemented() + else: + resolver.clean() + return "OK" + else: + [resolver.clean(**kwds) for resolver in self._dependency_resolvers if hasattr(resolver, 'clean')] + return "OK" diff --git a/lib/galaxy/webapps/galaxy/api/tool_dependencies.py b/lib/galaxy/webapps/galaxy/api/tool_dependencies.py index f91c93ff5a5..ea5821dfea3 100644 --- a/lib/galaxy/webapps/galaxy/api/tool_dependencies.py +++ b/lib/galaxy/webapps/galaxy/api/tool_dependencies.py @@ -162,3 +162,22 @@ class ToolDependenciesAPIController( BaseAPIController ): the corresponding resolver (keyed on 'index'). """ return self._view.manager_requirements() + + @expose_api + @require_admin + def clean(self, trans, id=None, **kwds): + """ + POST /api/dependencies_resolver/{index}/clean + + Cleans up intermediate files created by resolvers during the dependency + installation. + + :type index: int + :param index: index of the dependency resolver + + :rtype: dict + :returns: a dictified description of the requirement that could + be resolved (keyed on 'requirement') and the index of + the corresponding resolver (keyed on 'index'). + """ + return self._view.clean(id, **kwds) diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 8dc7372738c..b76dd9a3f40 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -129,6 +129,40 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ): tool = self._get_tool(id) return tool.tool_requirements_status + @expose_api + @web.require_admin + def install_dependencies(self, trans, id, **kwds): + """ + POST /api/tools/{tool_id}/install_dependencies + Attempts to install requirements via the dependency resolver + + parameters: + build_dependency_cache: If true, attempts to cache dependencies for this tool + force_rebuild: If true and chache dir exists, attempts to delete cache dir + """ + tool = self._get_tool(id) + [tool._view.install_dependency(id=None, **req.to_dict()) for req in tool.requirements] + if kwds.get('build_dependency_cache'): + tool.build_dependency_cache(**kwds) + # TODO: rework resolver install system to log and report what has been done. + # _view.install_dependency should return a dict with stdout, stderr and success status + return tool.tool_requirements_status + + @expose_api + @web.require_admin + def build_dependency_cache(self, trans, id, **kwds): + """ + POST /api/tools/{tool_id}/build_dependency_cache + Attempts to cache installed dependencies. + + parameters: + force_rebuild: If true and chache dir exists, attempts to delete cache dir + """ + tool = self._get_tool(id) + tool.build_dependency_cache(**kwds) + # TODO: Should also have a more meaningful return. + return tool.tool_requirements_status + @expose_api @web.require_admin def diagnostics( self, trans, id, **kwd ): diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 53d0c948383..8bee0a15ddb 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -262,12 +262,16 @@ def populate_api_routes( webapp, app ): webapp.mapper.connect( '/api/tools/{id:.+?}/citations', action='citations', controller="tools" ) webapp.mapper.connect( '/api/tools/{id:.+?}/download', action='download', controller="tools" ) webapp.mapper.connect( '/api/tools/{id:.+?}/requirements', action='requirements', controller="tools") + webapp.mapper.connect( '/api/tools/{id:.+?}/install_dependencies', action='install_dependencies', controller="tools", conditions=dict( method=[ "POST" ] )) + webapp.mapper.connect( '/api/tools/{id:.+?}/build_dependency_cache', action='build_dependency_cache', controller="tools", conditions=dict( method=[ "POST" ] )) webapp.mapper.connect( '/api/tools/{id:.+?}', action='show', controller="tools" ) webapp.mapper.resource( 'tool', 'tools', path_prefix='/api' ) + webapp.mapper.connect( '/api/dependency_resolvers/clean', action="clean", controller="tool_dependencies", conditions=dict( method=[ "POST" ]) ) webapp.mapper.connect( '/api/dependency_resolvers/dependency', action="manager_dependency", controller="tool_dependencies", conditions=dict( method=[ "GET" ] ) ) webapp.mapper.connect( '/api/dependency_resolvers/dependency', action="install_dependency", controller="tool_dependencies", conditions=dict( method=[ "POST" ] ) ) webapp.mapper.connect( '/api/dependency_resolvers/requirements', action="manager_requirements", controller="tool_dependencies" ) + webapp.mapper.connect( '/api/dependency_resolvers/{id}/clean', action="clean", controller="tool_dependencies", conditions=dict( method=[ "POST" ]) ) webapp.mapper.connect( '/api/dependency_resolvers/{id}/dependency', action="resolver_dependency", controller="tool_dependencies", conditions=dict( method=[ "GET" ] ) ) webapp.mapper.connect( '/api/dependency_resolvers/{id}/dependency', action="install_dependency", controller="tool_dependencies", conditions=dict( method=[ "POST" ] ) ) webapp.mapper.connect( '/api/dependency_resolvers/{id}/requirements', action="resolver_requirements", controller="tool_dependencies" ) diff --git a/lib/tool_shed/galaxy_install/install_manager.py b/lib/tool_shed/galaxy_install/install_manager.py index 699797dcbd9..430986f2339 100644 --- a/lib/tool_shed/galaxy_install/install_manager.py +++ b/lib/tool_shed/galaxy_install/install_manager.py @@ -904,6 +904,15 @@ class InstallRepositoryManager( object ): self.install_model.context.refresh( tool_shed_repository ) metadata = tool_shed_repository.metadata if 'tools' in metadata: + if install_resolver_dependencies: + requirements = suc.get_unique_requirements_from_repository(tool_shed_repository) + [self._view.install_dependency(id=None, **req) for req in requirements] + cached_requirements = [] + for tool_d in metadata['tools']: + tool = self.app.toolbox._tools_by_id.get(tool_d['guid'], None) + if tool and tool.requirements not in cached_requirements: + cached_requirements.append(tool.requirements) + tool.build_dependency_cache() # Get the tool_versions from the tool shed for each tool in the installed change set. self.update_tool_shed_repository_status( tool_shed_repository, self.install_model.ToolShedRepository.installation_status.SETTING_TOOL_VERSIONS ) @@ -913,9 +922,6 @@ class InstallRepositoryManager( object ): error_message += "Version information for the tools included in the %s repository is missing. " % tool_shed_repository.name error_message += "Reset all of this repository's metadata in the tool shed, then set the installed tool versions " error_message += "from the installed repository's Repository Actions menu. " - if install_resolver_dependencies: - requirements = suc.get_unique_requirements_from_repository(tool_shed_repository) - [self._view.install_dependency(id=None, **req) for req in requirements] if install_tool_dependencies and tool_shed_repository.tool_dependencies and 'tool_dependencies' in metadata: work_dir = tempfile.mkdtemp( prefix="tmp-toolshed-itsr" ) # Install tool dependencies. diff --git a/test/integration/test_resolvers.py b/test/integration/test_resolvers.py index f1ee7d9b8eb..5b134ac54ee 100644 --- a/test/integration/test_resolvers.py +++ b/test/integration/test_resolvers.py @@ -17,6 +17,7 @@ class CondaResolutionIntegrationTestCase(integration_util.IntegrationTestCase, A @classmethod def handle_galaxy_config_kwds(cls, config): cls.conda_tmp_prefix = mkdtemp() + config["use_cached_dep_manager"] = True config["conda_auto_init"] = True config["conda_prefix"] = os.path.join(cls.conda_tmp_prefix, 'conda') @@ -82,3 +83,22 @@ class CondaResolutionIntegrationTestCase(integration_util.IntegrationTestCase, A self._assert_status_code_is( create_response, 200 ) response = create_response.json() assert response['dependency_type'] == 'conda' and not response['exact'] + + def test_conda_install_through_tools_api( self ): + tool_id = 'mulled_example_multi_1' + endpoint = "tools/%s/install_dependencies" % tool_id + data = {'id': tool_id} + create_response = self._post(endpoint, data=data, admin=True) + self._assert_status_code_is( create_response, 200 ) + response = create_response.json() + assert any([True for d in response if d['dependency_type'] == 'conda']) + endpoint = "tools/%s/build_dependency_cache" % tool_id + create_response = self._post(endpoint, data=data, admin=True) + self._assert_status_code_is( create_response, 200 ) + + def test_conda_clean( self ): + endpoint = 'dependency_resolvers/clean' + create_response = self._post(endpoint, data={}, admin=True) + self._assert_status_code_is(create_response, 200) + response = create_response.json() + assert response == "OK"