Commits

John Chilton  committed ad17d92

Create and utilize a common interface for managing Galaxy's settings (default single file mode versus configuration directory mode). The benefit of this is that the functionality I had added like configure_multiple_processes, galaxy_universe_***, galaxy_tool_runner_***, etc... should no longer require a configuration directory be set, and conversely the recent work that has been done regarding path management will work even when a configuration directory is set.

Also added a unit testing setup (setup nose and coverage and a little writeup in TESTING.md). Refactored all logic related to setting up Galaxy properties out into the new cm.util.galaxy_conf module. Included a fairly complete set of unit tests for this new module in test/test_galaxy_conf.py.

  • Participants
  • Parent commits b5148b3

Comments (0)

Files changed (10)

+[run]
+branch = True
+include = cm/*
 
 # For vim
 *.swp
+
+# Dev files
+.coverage
+htmlcov
+CloudMan Development
+--------------------
+
+Setup your shell environment for CloudMan development (only need to do this once):
+
+    wget -qO- https://raw.github.com/brainsik/virtualenv-burrito/master/virtualenv-burrito.sh | $SHELL
+    . $HOME/.venvburrito/startup.sh
+    mkvirtualenv cm
+    workon cm
+    pip install -r requirements.txt -r dev_requirements.txt
+
+Load CloudMan virtual envrionment (once per shell):
+
+    . $HOME/.venvburrito/startup.sh
+    workon cm
+
+Run tests:
+
+    nosetests
+

File __init__.py

-"""The CloudMan Application."""
-
-from cm.framework import expose, url_for

File cm/services/apps/galaxy.py

 import os
-import pwd
-import grp
 import shutil
 import urllib2
 import subprocess
 from datetime import datetime
 from string import Template
-from ConfigParser import SafeConfigParser
 
 from cm.services.apps import ApplicationService
 from cm.services import service_states
 from cm.util import paths
 from cm.util import misc
 from cm.util import templates
+from cm.util.galaxy_conf import attempt_chown_galaxy, attempt_chown_galaxy_if_exists
+from cm.util.galaxy_conf import galaxy_option_manager
+from cm.util.galaxy_conf import populate_process_options
+from cm.util.galaxy_conf import populate_dynamic_options
+from cm.util.galaxy_conf import populate_galaxy_paths
+from cm.util.galaxy_conf import populate_admin_users
 
 import logging
 log = logging.getLogger('cloudman')
                      ServiceDependency(self, ServiceRole.GALAXY_INDICES),
                      ServiceDependency(self, ServiceRole.GALAXY_TOOLS)
                      ]
+        self.option_manager = galaxy_option_manager(app)
 
     @property
     def galaxy_home(self):
         log.debug("Using Galaxy from '{0}'".format(self.galaxy_home))
         os.putenv("GALAXY_HOME", self.galaxy_home)
         os.putenv("TEMP", self.app.path_resolver.galaxy_temp)
-        # Setup configuration directory for galaxy if galaxy_conf_dir specified
-        # in user-data.
-        if self.has_config_dir():
-            self.setup_config_dir()
-        # TODO: Pick better name
-        if self.app.ud.get("configure_multiple_galaxy_processes", False):
-            self.configure_multiple_galaxy_processes()
+        conf_dir = self.option_manager.setup()
+        if conf_dir:
+            self.env_vars["GALAXY_UNIVERSE_CONFIG_DIR"] = conf_dir
+
+        if self._multiple_processes():
+            self.env_vars["GALAXY_RUN_ALL"] = "TRUE"
+            # HACK: Galaxy has a known problem when starting from a fresh configuration
+            # in multiple process mode. Each process attempts to create the same directories
+            # and one or more processes can fail to start because it "failed" to create
+            # said directories (because another process created them first). This hack staggers
+            # the process starts in an attempt to circumvent this problem.
+            patch_run_sh_command = "sudo sed -i -e \"s/server.log \\$\\@$/\\0; sleep 10/\" %s/run.sh" % self.galaxy_home
+            misc.run(patch_run_sh_command)
             self.extra_daemon_args = ""
         else:
             # Instead of sticking with default paster.pid and paster.log, explicitly
                                                       self.app.ud[
                                                       'bucket_default'],
                                                       '{0}.cloud'.format(f_name), local_file)
-                            self._attempt_chown_galaxy_if_exists(local_file)
-                self.add_dynamic_galaxy_options()
+                            attempt_chown_galaxy_if_exists(local_file)
+
                 # Make sure the temporary job_working_directory exists on user
                 # data volume (defined in universe_wsgi.ini.cloud)
                 if not os.path.exists('%s/tmp/job_working_directory' % self.app.path_resolver.galaxy_data):
                     os.makedirs('%s/tmp/job_working_directory/' %
                                 self.app.path_resolver.galaxy_data)
-                self._attempt_chown_galaxy(
+                attempt_chown_galaxy(
                     '%s/tmp/job_working_directory/' % self.app.path_resolver.galaxy_data)
                 # Setup environment for the FTP server and start it
                 if not os.path.exists('%s/tmp/ftp' % self.app.path_resolver.galaxy_data):
                 subprocess.call("bash -c 'for f in $GALAXY_HOME/{main,handler,manager,web}*.log; do mv \"$f\" \"$f.%s\"; done'" % datetime.utcnow().strftime(
                     '%H_%M'), shell=True)
 
+    def _multiple_processes(self):
+        return self.app.ud.get("configure_multiple_galaxy_processes", False)
+
     def galaxy_run_command(self, args):
         env_exports = "; ".join(["export %s='%s'" % (
             key, value) for key, value in self.env_vars.iteritems()])
         except:
             return False
 
-    def has_config_dir(self):
-        return self.app.ud.get("galaxy_conf_dir", None) is not None
-
-    def setup_config_dir(self):
-        conf_dir = self.get_galaxy_conf_dir()
-        GalaxyService.initialize_galaxy_config_dir(
-            conf_dir, 'universe_wsgi.ini')
-        # This will ensure galaxy's run.sh file picks up the config dir.
-        self.env_vars["GALAXY_UNIVERSE_CONFIG_DIR"] = conf_dir
-
-    @staticmethod
-    def initialize_galaxy_config_dir(conf_dir, defaults_name):
-        # If config dir does not exist, create it and put default
-        # properties in with low priority.
-        if not os.path.exists(conf_dir):
-            os.makedirs(conf_dir)
-            defaults_destination = os.path.join(
-                conf_dir, "010_%s" % defaults_name)
-            universe_wsgi = os.path.join(
-                self.app.path_resolver.galaxy_home, defaults_name)
-            if not os.path.exists(universe_wsgi):
-                # Fresh install, take the oppertunity to just link in defaults
-                defaults_source = os.path.join(
-                    self.app.path_resolver.galaxy_home, "%s.sample" % defaults_name)
-                os.symlink(defaults_source, defaults_destination)
-            else:
-                # CloudMan has previously been run without the galaxy_conf_dir
-                # option enabled. Users may have made modifications to universe_wsgi.ini
-                # that I guess we should preserve for backward compatibility.
-                defaults_source = os.path.join(
-                    self.app.path_resolver.galaxy_home, defaults_name)
-                shutil.copyfile(defaults_source, defaults_destination)
-
-    def get_galaxy_conf_dir(self):
-        return self.app.ud.get("galaxy_conf_dir", None)
-
-    def configure_multiple_galaxy_processes(self):
-        if not self.has_config_dir():
-            log.warn(
-                "Must specify a galaxy configuration directory (via galaxy_conf_dir) in order to create a multiple Galaxy processes.")
-            return
-        web_thread_count = int(self.app.ud.get("web_thread_count", 1))
-        handler_thread_count = int(self.app.ud.get("handler_thread_count", 1))
-        [self.add_server_process(
-            i, "web", 8080) for i in range(web_thread_count)]
-        handlers = [self.add_server_process(
-            i, "handler", 9080) for i in range(handler_thread_count)]
-        self.add_server_process(0, "manager", 8079)
-        self.add_universe_option("job_manager", "manager0")
-        self.add_universe_option("job_handlers", ",".join(handlers))
-        self.env_vars["GALAXY_RUN_ALL"] = "TRUE"
-
-        # HACK: Galaxy has a known problem when starting from a fresh configuration
-        # in multiple process mode. Each process attempts to create the same directories
-        # and one or more processes can fail to start because it "failed" to create
-        # said directories (because another process created them first). This hack staggers
-        # the process starts in an attempt to circumvent this problem.
-        patch_run_sh_command = "sudo sed -i -e \"s/server.log \\$\\@$/\\0; sleep 10/\" %s/run.sh" % self.galaxy_home
-        misc.run(patch_run_sh_command)
-
-    def add_server_process(self, index, prefix, initial_port):
-        port = initial_port + index
-        server_options = {"use": "egg:Paste#http",
-                          "port": port,
-                          "use_threadpool": True,
-                          "threadpool_workers": self.app.ud.get("threadpool_workers", "7")
-                          }
-        server_name = "%s%d" % (prefix, index)
-        if port == 8080:
-            # Special case, server on port 8080 must be called main unless we want
-            # to start deleting chunks of universe_wsgi.ini.
-            server_name = "main"
-        self.add_universe_options(server_options, "server_%s" % server_name,
-                                  section="server:%s" % server_name)
-        return server_name
-
-    def add_universe_option(self, name, value, section="app:main"):
-        options = {name: value}
-        self.add_universe_options(options, name, section)
-
-    def add_universe_options(self, options, description, section="app:main"):
-        prefix = self.app.ud.get("option_priority", "400")
-        conf_dir = self.get_galaxy_conf_dir()
-        conf_file_name = "%s_cloudman_override_%s.ini" % (prefix, description)
-        conf_file = os.path.join(conf_dir, conf_file_name)
-        props_str = "\n".join(
-            ["%s=%s" % (k, v) for k, v in options.iteritems()])
-        open(conf_file, "w").write("[%s]\n%s" % (section, props_str))
-
-    def add_dynamic_galaxy_options(self):
-        if not self.has_config_dir():
-            return False
-        dynamic_option_types = {"galaxy_universe_": "app:main",
-                                "galaxy_tool_runner_": "galaxy:tool_runners",
-                                }
-        for option_prefix, section in dynamic_option_types.iteritems():
-            for key, value in self.app.ud.iteritems():
-                if key.startswith(option_prefix):
-                    key = key[len(option_prefix):]
-                    self.add_universe_option(key, value, section)
-
     def update_galaxy_config(self):
-        self.set_galaxy_paths()
-        self.add_galaxy_admin_users()
-
-    def set_galaxy_paths(self):
-        section = "app:main"
-        config_file_path = os.path.join(self.galaxy_home, "universe_wsgi.ini")
-        parser = SafeConfigParser()
-        with open(config_file_path, 'rt') as configfile:
-            parser.readfp(configfile)
-            parser.set(section, "genome_data_path", os.path.join(
-                self.app.path_resolver.galaxy_indices, "genomes"))
-            parser.set(section, "len_file_path", os.path.join(
-                self.app.path_resolver.galaxy_indices, "len"))
-            parser.set(section, "tool_dependency_dir", os.path.join(
-                self.app.path_resolver.galaxy_tools, "tools"))
-            parser.set(section, "file_path", os.path.join(
-                self.app.path_resolver.galaxy_data, "files"))
-            temp_dir = os.path.join(self.app.path_resolver.galaxy_data, "tmp")
-            parser.set(section, "new_file_path", temp_dir)
-            parser.set(section, "job_working_directory",
-                       os.path.join(temp_dir, "job_working_directory"))
-            parser.set(section, "cluster_files_directory",
-                       os.path.join(temp_dir, "pbs"))
-            parser.set(
-                section, "ftp_upload_dir", os.path.join(temp_dir, "ftp"))
-            parser.set(section, "nginx_upload_store", os.path.join(
-                self.app.path_resolver.galaxy_data, "upload_store"))
-        with open(config_file_path, 'wt') as configfile:
-            parser.write(configfile)
+        if self._multiple_processes():
+            populate_process_options(self.option_manager)
+        populate_dynamic_options(self.option_manager)
+        populate_galaxy_paths(self.option_manager)
+        populate_admin_users(self.option_manager)
 
     def add_galaxy_admin_users(self, admins_list=[]):
-        """ Galaxy admin users can now be added by providing them in user data
-            (see below) or by calling this method and providing a user list.
-            YAML format for user data for providing admin users
-            (note that these users will still have to manually register on the given cloud instance):
-            admin_users:
-             - user@example.com
-             - user2@anotherexample.edu """
-        for admin in self.app.ud.get('admin_users', []):
-            if admin not in admins_list:
-                    admins_list.append(admin)
-        if len(admins_list) == 0:
-            return False
-        log.info('Adding Galaxy admin users: %s' % admins_list)
-        if self.has_config_dir():
-            self.add_universe_option("admin_users", ",".join(admins_list))
-        else:
-            config_file_path = os.path.join(
-                self.galaxy_home, 'universe_wsgi.ini')
-            new_config_file_path = os.path.join(
-                self.galaxy_home, 'universe_wsgi.ini.new')
-            admins_str = ', '.join(str(a) for a in admins_list)
-            with open(config_file_path, 'rt') as configfile:
-                parser = SafeConfigParser()
-                parser.readfp(configfile)
-                parser.set("app:main", "admin_users", admins_str)
-            with open(new_config_file_path, 'wt') as output_file:
-                parser.write(output_file)
-            shutil.move(new_config_file_path, config_file_path)
-            # Change the owner of the file to galaxy user
-            self._attempt_chown_galaxy(config_file_path)
-
-    def _attempt_chown_galaxy_if_exists(self, path):
-        if os.path.exists(path):
-            self._attempt_chown_galaxy(path)
-
-    def _attempt_chown_galaxy(self, path):
-        try:
-            galaxy_uid = pwd.getpwnam("galaxy")[2]
-            galaxy_gid = grp.getgrnam("galaxy")[2]
-            os.chown(path, galaxy_uid, galaxy_gid)
-        except OSError:
-            misc.run("chown galaxy:galaxy '%s'" % path)
+        populate_admin_users(self.option_manager, admins_list)
 
     def configure_nginx(self):
         """

File cm/util/galaxy_conf.py

+from os.path import join, exists
+from os import makedirs, symlink, chown
+from shutil import copyfile, move
+
+from ConfigParser import SafeConfigParser
+from pwd import getpwnam
+from grp import getgrnam
+
+from .misc import run
+
+import logging
+log = logging.getLogger('cloudman')
+
+
+OPTIONS_FILE_NAME = 'universe_wsgi.ini'
+
+
+def attempt_chown_galaxy_if_exists(path):
+    """
+    Change owner of file at specified `path` (if it exists)
+    to `galaxy`.
+    """
+    if exists(path):
+        attempt_chown_galaxy(path)
+
+
+def attempt_chown_galaxy(path):
+    """
+    Change owner of file at specified `path` to `galaxy`.
+    """
+    try:
+        galaxy_uid = getpwnam("galaxy")[2]
+        galaxy_gid = getgrnam("galaxy")[2]
+        chown(path, galaxy_uid, galaxy_gid)
+    except BaseException:
+        run("chown galaxy:galaxy '%s'" % path)
+
+
+def populate_admin_users(option_manager, admins_list=[]):
+    """ Galaxy admin users can now be added by providing them in user data
+        (see below) or by calling this method and providing a user list.
+        YAML format for user data for providing admin users
+        (note that these users will still have to manually register
+        on the given cloud instance):
+        admin_users:
+         - user@example.com
+         - user2@anotherexample.edu """
+    for admin in option_manager.app.ud.get('admin_users', []):
+        if admin not in admins_list:
+                admins_list.append(admin)
+    if len(admins_list) == 0:
+        return False
+    log.info('Adding Galaxy admin users: %s' % admins_list)
+    option_manager.set_properties({"admin_users": ",".join(admins_list)})
+
+
+def populate_dynamic_options(option_manager):
+    """
+    Use `option_manager` to populate arbitrary app:main and galaxy:tool_runners
+    properties coming in from userdata.
+    """
+    dynamic_option_types = {"galaxy_universe_": "app:main",
+                            "galaxy_tool_runner_": "galaxy:tool_runners",
+                            }
+    for option_prefix, section in dynamic_option_types.iteritems():
+        for key, value in option_manager.app.ud.iteritems():
+            if key.startswith(option_prefix):
+                key = key[len(option_prefix):]
+                option_manager.set_properties({key: value}, section=section)
+
+
+## High-level funcitons that utilize option_manager interface (defined below)
+## to configure Galaxy's options.
+def populate_process_options(option_manager):
+    """
+    Use `option_manager` to populate process (handler, manager, web) sections
+    for Galaxy.
+    """
+    app = option_manager.app
+    web_thread_count = int(app.ud.get("web_thread_count", 1))
+    handler_thread_count = int(app.ud.get("handler_thread_count", 1))
+    # Setup web threads
+    [__add_server_process(option_manager, i, "web", 8080) \
+        for i in range(web_thread_count)]
+    # Setup handler threads
+    handlers = [__add_server_process(option_manager, i, "handler", 9080) \
+        for i in range(handler_thread_count)]
+    # Setup manager thread
+    __add_server_process(option_manager, 0, "manager", 8079)
+    process_properties = {"job_manager": "manager0",
+                          "job_handlers": ",".join(handlers)}
+    option_manager.set_properties(process_properties)
+
+
+def __add_server_process(option_manager, index, prefix, initial_port):
+    app = option_manager.app
+    port = initial_port + index
+    threads = app.ud.get("threadpool_workers", "7")
+    server_options = {"use": "egg:Paste#http",
+                      "port": port,
+                      "use_threadpool": True,
+                      "threadpool_workers": threads
+                      }
+    server_name = "%s%d" % (prefix, index)
+    if port == 8080:
+        # Special case, server on port 8080 must be called main unless we want
+        # to start deleting chunks of universe_wsgi.ini.
+        server_name = "main"
+    option_manager.set_properties(server_options,
+                                  section="server:%s" % server_name,
+                                  description="server_%s" % server_name)
+    return server_name
+
+
+## Abstraction for interacting with Galaxy's options
+def galaxy_option_manager(app):
+    """ Returns a high-level class for managing Galaxy options.
+    """
+    ud = app.ud
+    if "galaxy_conf_dir" in ud:
+        option_manager = DirectoryGalaxyOptionManager(app)
+    else:
+        option_manager = FileGalaxyOptionManager(app)
+    return option_manager
+
+
+def populate_galaxy_paths(option_manager):
+    """
+    Turn `path_resolver` paths into Galaxy options using
+    specified `option_manager`.
+    """
+    properties = {}
+    path_resolver = option_manager.app.path_resolver
+    properties["genome_data_path"] = \
+        join(path_resolver.galaxy_indices, "genomes")
+    properties["len_file_path"] = \
+        join(path_resolver.galaxy_indices, "len")
+    properties["tool_dependency_dir"] = \
+        join(path_resolver.galaxy_tools, "tools")
+    properties["file_path"] = join(path_resolver.galaxy_data, "files")
+    temp_dir = join(path_resolver.galaxy_data, "tmp")
+    properties["new_file_path"] = temp_dir
+    properties["job_working_directory"] = \
+        join(temp_dir, "job_working_directory")
+    properties["cluster_files_directory"] = \
+        join(temp_dir, "pbs")
+    properties["ftp_upload_dir"] = \
+        join(temp_dir, "ftp")
+    properties["nginx_upload_store"] = \
+        join(path_resolver.galaxy_data, "upload_store")
+    option_manager.set_properties(properties, description="paths")
+
+
+class FileGalaxyOptionManager(object):
+    """
+    Default Galaxy option manager, modifies $galaxy_home/universe_wsgi
+    directly.
+    """
+
+    def __init__(self, app):
+        self.app = app
+
+    def setup(self):
+        """ setup should return conf_dir, in this case there is none."""
+        return None
+
+    def set_properties(self, properties, section="app:main", description=None):
+        galaxy_home = self.app.path_resolver.galaxy_home
+        config_file_path = join(galaxy_home, OPTIONS_FILE_NAME)
+        parser = SafeConfigParser()
+        configfile = open(config_file_path, 'rt')
+        parser.readfp(configfile)
+        for key, value in properties.iteritems():
+            parser.set(section, key, value)
+        configfile.close()
+        new_config_file_path = join(galaxy_home, 'universe_wsgi.ini.new')
+        with open(new_config_file_path, 'wt') as output_file:
+                parser.write(output_file)
+        move(new_config_file_path, config_file_path)
+        attempt_chown_galaxy(config_file_path)
+
+
+class DirectoryGalaxyOptionManager(object):
+    """
+    When `galaxy_conf_dir` in specified in UserData this is used to
+    manage Galaxy's options.
+    """
+
+    def __init__(self, app):
+        self.app = app
+        conf_dir = app.ud["galaxy_conf_dir"]
+        self.conf_dir = conf_dir
+
+    def setup(self):
+        """ Setup the configuration directory and return conf_dir. """
+        self.__initialize_galaxy_config_dir()
+        return self.conf_dir
+
+    def __initialize_galaxy_config_dir(self):
+        conf_dir = self.conf_dir
+        if not exists(conf_dir):
+            makedirs(conf_dir)
+            defaults_destination = join(conf_dir, "010_%s" % OPTIONS_FILE_NAME)
+            galaxy_home = self.app.path_resolver.galaxy_home
+            universe_wsgi = join(galaxy_home, OPTIONS_FILE_NAME)
+            if not exists(universe_wsgi):
+                # Fresh install, take the oppertunity to just link in defaults
+                sample_name = "%s.sample" % OPTIONS_FILE_NAME
+                defaults_source = join(galaxy_home, sample_name)
+                symlink(defaults_source, defaults_destination)
+            else:
+                # CloudMan has previously been run without the galaxy_conf_dir
+                # option enabled. Users may have made modifications to
+                # universe_wsgi.ini that I guess we should preserve for
+                # backward compatibility.
+                defaults_source = join(galaxy_home, OPTIONS_FILE_NAME)
+                copyfile(defaults_source, defaults_destination)
+
+    def set_properties(self, properties, section="app:main", description=None):
+        if not properties:
+            return
+
+        prefix = self.app.ud.get("galaxy_option_priority", "400")
+        conf_dir = self.conf_dir
+        if description == None:
+            description = properties.keys()[0]
+        conf_file_name = "%s_cloudman_override_%s.ini" % (prefix, description)
+        conf_file = join(conf_dir, conf_file_name)
+        props_str = "\n".join(
+            ["%s=%s" % (k, v) for k, v in properties.iteritems()])
+        open(conf_file, "w").write("[%s]\n%s" % (section, props_str))

File dev_requirements.txt

+## Requirements not needed by CloudMan runtime but useful for CloudMan
+## development.
+nose
+coverage
+# webtest
+# sphinx
+[nosetests]
+verbosity=1
+detailed-errors=1
+with-doctest=1
+with-coverage=1

File test/test_galaxy_conf.py

+from cm.util.galaxy_conf import galaxy_option_manager
+from cm.util.galaxy_conf import populate_process_options
+from cm.util.galaxy_conf import populate_dynamic_options
+from cm.util.galaxy_conf import populate_galaxy_paths
+from cm.util.galaxy_conf import populate_admin_users
+from test_utils import TestApp
+from test_utils import TEST_DATA_DIR, TEST_INDICES_DIR, TEST_TOOLS_DIR
+from tempfile import mkdtemp
+from os.path import join
+from ConfigParser import SafeConfigParser
+
+
+def test_file_option_manager():
+    app = TestApp()
+    option_manager = galaxy_option_manager(app)
+    option_manager.setup()
+    _set_test_property(option_manager)
+    test_galaxy_home = app.path_resolver.galaxy_home
+    universe_path = join(test_galaxy_home, 'universe_wsgi.ini')
+    with open(universe_path, 'rt') as f:
+        parser = SafeConfigParser()
+        parser.readfp(f)
+        assert parser.get('app:main', "admin_users") == "test@example.org"
+
+
+def test_dir_option_manager():
+    conf_dir = mkdtemp()
+    app = TestApp(ud={"galaxy_conf_dir": conf_dir})
+    option_manager = galaxy_option_manager(app)
+    option_manager.setup()
+    _set_test_property(option_manager)
+    option_file_path = join(conf_dir, '400_cloudman_override_admin_users.ini')
+    content = open(option_file_path, 'r').read()
+    assert content == "[app:main]\nadmin_users=test@example.org"
+
+
+def test_populate_process_options():
+    app = TestApp(ud={"web_thread_count": 3, "handler_thread_count": 2})
+    option_manager = TestOptionManager(app)
+    populate_process_options(option_manager)
+    options = option_manager.options
+    # Test correct sections configured
+    for name in ["web1", "web2", "handler0", "handler1", "manager0"]:
+        assert "server:%s" % name in options
+    for name in ["web3", "handler2"]:
+        assert "server:%s" % name not in options
+    ## TODO: Actually test configuration of sections, thread count, etc...
+
+
+def test_populate_dynamic_options():
+    test_connection = "mysql:///dbserver/galaxy"
+    test_runner = "pbs:///test1"
+    ud = {"galaxy_universe_database_connection": test_connection,
+          "galaxy_tool_runner_cooltool1": test_runner}
+    app = TestApp(ud=ud)
+    option_manager = TestOptionManager(app)
+    populate_dynamic_options(option_manager)
+    options = option_manager.options
+    assert options["galaxy:tool_runners"]["cooltool1"] == test_runner
+    assert options["app:main"]["database_connection"] == test_connection
+
+
+def test_populate_galaxy_paths():
+    app = TestApp()
+    option_manager = TestOptionManager(app)
+    populate_galaxy_paths(option_manager)
+    main_options = option_manager.options["app:main"]
+    assert main_options["genome_data_path"] == \
+        join(TEST_INDICES_DIR, "genomes")
+    assert main_options["len_file_path"] == \
+        join(TEST_INDICES_DIR, "len")
+    assert main_options["tool_dependency_dir"] == \
+        join(TEST_TOOLS_DIR, "tools")
+    assert main_options["file_path"] == \
+        join(TEST_DATA_DIR, "files")
+    assert main_options["new_file_path"] == \
+        join(TEST_DATA_DIR, "tmp")
+    assert main_options["job_working_directory"] == \
+        join(TEST_DATA_DIR, "tmp", "job_working_directory")
+    assert main_options["cluster_files_directory"] == \
+        join(TEST_DATA_DIR, "tmp", "pbs")
+    assert main_options["ftp_upload_dir"] == \
+        join(TEST_DATA_DIR, "tmp", "ftp")
+    assert main_options["nginx_upload_store"] == \
+        join(TEST_DATA_DIR, "upload_store")
+
+
+def test_populate_admin_users():
+    app = TestApp(ud={"admin_users": ["mary@example.com", "bill@example.com"]})
+    option_manager = TestOptionManager(app)
+    populate_admin_users(option_manager)
+    options = option_manager.options
+    assert options["app:main"]["admin_users"] == \
+        "mary@example.com,bill@example.com"
+
+
+class TestOptionManager(object):
+
+    def __init__(self, app):
+        self.app = app
+        self.options = {}
+
+    def set_properties(self, properties, section="app:main", description=None):
+        if not section in self.options:
+            self.options[section] = {}
+        self.options[section].update(properties)
+
+
+def _set_test_property(option_manager):
+    properties = {"admin_users": "test@example.org"}
+    option_manager.set_properties(properties)

File test/test_utils.py

+
+from tempfile import mkdtemp
+from os.path import join, dirname
+from os import pardir
+from shutil import copyfile
+
+
+TEST_INDICES_DIR = "/mnt/indicesTest"
+TEST_DATA_DIR = "/mnt/dataTest"
+TEST_TOOLS_DIR = "/mnt/toolsTest"
+
+
+class TestApp(object):
+    """ Dummy version of UniverseApplication used for
+    unit testing."""
+
+    def __init__(self, ud={}, **kwargs):
+        self.path_resolver = TestPathResolver()
+        self.ud = ud
+        for key, value in kwargs.iteritems():
+            setattr(self, key, value)
+
+
+class TestPathResolver(object):
+    """ Dummy implementation of PathResolver
+    for unit testing."""
+
+    def __init__(self):
+        self._galaxy_home = None
+        self.galaxy_data = TEST_DATA_DIR
+        self.galaxy_tools = TEST_TOOLS_DIR
+        self.galaxy_indices = TEST_INDICES_DIR
+
+    @property
+    def galaxy_home(self):
+        if not self._galaxy_home:
+            self._galaxy_home = mkdtemp()
+            cm_directory = join(dirname(__file__), pardir)
+            src = join(cm_directory, 'universe_wsgi.ini.cloud')
+            dest = join(self._galaxy_home, 'universe_wsgi.ini')
+            copyfile(src, dest)
+            copyfile(src, '%s.sample' % dest)
+        return self._galaxy_home