Skip to content

Commit

Permalink
Merge pull request #32 from mvdbeek/fixes_for_resolved_symlink
Browse files Browse the repository at this point in the history
Support managing gx-it-proxy via gravity
  • Loading branch information
mvdbeek authored Mar 4, 2022
2 parents 89e64f7 + d770046 commit 539fd35
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 22 deletions.
5 changes: 3 additions & 2 deletions gravity/commands/cmd_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@


@click.command("update")
@click.option("--force", is_flag=True, help="Force rewriting of process config files")
@click.pass_context
def cli(ctx):
def cli(ctx, force):
"""Update process manager from config changes."""
with process_manager.process_manager(state_dir=ctx.parent.state_dir, start_daemon=False) as pm:
pm.update()
pm.update(force)
18 changes: 17 additions & 1 deletion gravity/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
CELERY_DEFAULT_CONFIG,
DEFAULT_INSTANCE_NAME,
GUNICORN_DEFAULT_CONFIG,
GXIT_DEFAULT_IP,
GXIT_DEFAULT_PORT,
GXIT_DEFAULT_SESSIONS,
)
from gravity.io import debug, error, exception, info, warn
from gravity.state import (
Expand Down Expand Up @@ -85,6 +88,7 @@ def get_config(self, conf, defaults=None):
"app_server": "gunicorn",
"gunicorn": GUNICORN_DEFAULT_CONFIG,
"celery": CELERY_DEFAULT_CONFIG,
"gx_it_proxy": {},
"handlers": {},
}
if defaults is not None:
Expand All @@ -109,6 +113,7 @@ def get_config(self, conf, defaults=None):
config.attribs["gunicorn"] = gravity_config["gunicorn"]
config.attribs["celery"] = gravity_config["celery"]
config.attribs["handlers"] = gravity_config["handlers"]
config.attribs["gx_it_proxy"] = gravity_config["gx_it_proxy"]
# Store gravity version, in case we need to convert old setting
config.attribs['gravity_version'] = __version__
webapp_service_names = []
Expand All @@ -129,7 +134,6 @@ def get_config(self, conf, defaults=None):
config.services.append(service_for_service_type("celery")(config_type=config.config_type))
config.services.append(service_for_service_type("celery-beat")(config_type=config.config_type))
# If this is a Galaxy config, parse job_conf.xml for any *static* standalone handlers
# Marius: Don't think that's gonna work if job config file not defined!
# TODO: use galaxy config parsing ?
# TODO: if not, need yaml job config parsing
job_conf_xml = app_config.get("job_config_file", DEFAULT_JOB_CONFIG_FILE)
Expand All @@ -149,6 +153,7 @@ def get_config(self, conf, defaults=None):
# doesn't parse that part of the job config. See logic in lib/galaxy/web_stack/handlers.py _get_is_handler() to
# see how this is determined.
self.create_handler_services(gravity_config, config)
self.create_gxit_services(gravity_config, app_config, config)
return config

def create_handler_services(self, gravity_config, config):
Expand All @@ -158,6 +163,17 @@ def create_handler_services(self, gravity_config, config):
config.services.append(
service_for_service_type("standalone")(config_type=config.config_type, service_name=service_name, server_pools=pools))

def create_gxit_services(self, gravity_config, app_config, config):
if app_config.get("interactivetools_enable") and gravity_config["gx_it_proxy"].get("enable"):
# TODO: resolve against data_dir, or bring in galaxy-config ?
# CWD in supervisor template is galaxy_root, so this should work for simple cases as is
gxit_config = gravity_config['gx_it_proxy']
gxit_config["sessions"] = app_config.get("interactivetools_map", GXIT_DEFAULT_SESSIONS)
gxit_config["ip"] = gxit_config.get("ip", GXIT_DEFAULT_IP)
gxit_config["port"] = gxit_config.get("port", GXIT_DEFAULT_PORT)
gxit_config["verbose"] = '--verbose' if gxit_config.get("verbose") else ''
config.services.append(service_for_service_type("gx-it-proxy")(config_type=config.config_type, gxit=gxit_config))

@staticmethod
def expand_handlers(gravity_config, config):
handlers = gravity_config.get("handlers", {})
Expand Down
3 changes: 3 additions & 0 deletions gravity/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
"concurrency": 2,
"extra_args": ""
}
GXIT_DEFAULT_IP = "localhost"
GXIT_DEFAULT_PORT = 4002
GXIT_DEFAULT_SESSIONS = "database/interactivetools_map.sqlite"
30 changes: 26 additions & 4 deletions gravity/process_manager/supervisor_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,27 @@
{process_name_opt}
"""


SUPERVISORD_SERVICE_TEMPLATES["gx-it-proxy"] = """;
; This file is maintained by Galaxy - CHANGES WILL BE OVERWRITTEN
;
[program:{program_name}]
command = {command}
directory = {galaxy_root}
umask = {galaxy_umask}
autostart = true
autorestart = true
startsecs = 10
stopwaitsecs = 10
environment = npm_config_yes=true
numprocs = 1
stdout_logfile = {log_file}
redirect_stderr = true
{process_name_opt}
"""


SUPERVISORD_SERVICE_TEMPLATES["standalone"] = """;
; This file is maintained by Galaxy - CHANGES WILL BE OVERWRITTEN
;
Expand Down Expand Up @@ -237,6 +258,7 @@ def __update_service(self, config_file, config, attribs, service, instance_conf_
"attach_to_pool_opt": attach_to_pool_opt,
"gunicorn": attribs["gunicorn"],
"celery": attribs["celery"],
"gx_it_proxy": attribs["gx_it_proxy"],
"galaxy_umask": service.get("umask", "022"),
"program_name": program_name,
"process_name_opt": process_name_opt,
Expand All @@ -258,7 +280,7 @@ def __update_service(self, config_file, config, attribs, service, instance_conf_
with open(conf, "w") as out:
out.write(template.format(**format_vars))

def _process_config_changes(self, configs, meta_changes):
def _process_config_changes(self, configs, meta_changes, force=False):
# remove the services of any configs which have been removed
for config in meta_changes["remove_configs"].values():
instance_name = config["instance_name"]
Expand All @@ -273,7 +295,7 @@ def _process_config_changes(self, configs, meta_changes):
for config_file, config in configs.items():
instance_name = config["instance_name"]
attribs = config["attribs"]
update_all_configs = False
update_all_configs = False or force

# config attribs have changed (galaxy_root, virtualenv, etc.)
if "update_attribs" in config:
Expand Down Expand Up @@ -410,10 +432,10 @@ def shutdown(self):
time.sleep(0.5)
info("supervisord has terminated")

def update(self):
def update(self, force=False):
"""Add newly defined servers, remove any that are no longer present"""
configs, meta_changes = self.config_manager.determine_config_changes()
self._process_config_changes(configs, meta_changes)
self._process_config_changes(configs, meta_changes, force)
# only need to update if supervisord is running, otherwise changes will be picked up at next start
if self.__supervisord_is_running():
self.supervisorctl("update")
Expand Down
8 changes: 8 additions & 0 deletions gravity/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ class GalaxyCeleryBeatService(Service):
command_template = "{virtualenv_bin}celery --app galaxy.celery beat --loglevel {celery[loglevel]}"


class GalaxyGxItProxyService(Service):
service_type = "gx-it-proxy"
service_name = "gx-it-proxy"
command_template = "{virtualenv_bin}npx gx-it-proxy --ip {gx_it_proxy[ip]} --port {gx_it_proxy[port]}" \
" --sessions {gx_it_proxy[sessions]} {gx_it_proxy[verbose]}"


class GalaxyStandaloneService(Service):
service_type = "standalone"
service_name = "standalone"
Expand Down Expand Up @@ -164,5 +171,6 @@ def service_for_service_type(service_type):
"unicornherder": GalaxyUnicornHerderService,
"celery": GalaxyCeleryService,
"celery-beat": GalaxyCeleryBeatService,
"gx-it-proxy": GalaxyGxItProxyService,
"standalone": GalaxyStandaloneService,
}
32 changes: 32 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,26 @@
from pathlib import Path

import pytest
import yaml
from gravity import config_manager

TEST_DIR = Path(os.path.dirname(__file__))
GXIT_CONFIG = """
gravity:
gunicorn:
bind: 'localhost:{gx_port}'
gx_it_proxy:
enable: true
port: {gxit_port}
verbose: true
galaxy:
conda_auto_init: false
interactivetools_enable: true
interactivetools_map: database/interactivetools_map.sqlite
galaxy_infrastructure_url: http://localhost:{gx_port}
interactivetools_upstream_proxy: false
interactivetools_proxy_host: localhost:{gxit_port}
"""


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -77,6 +94,9 @@ def free_port():
return portnum


another_free_port = free_port


@pytest.fixture()
def startup_config(galaxy_virtualenv, free_port):
return {
Expand All @@ -91,6 +111,18 @@ def startup_config(galaxy_virtualenv, free_port):
}


@pytest.fixture
def gxit_config(free_port, another_free_port):
config_yaml = GXIT_CONFIG.format(gxit_port=another_free_port, gx_port=free_port)
return yaml.safe_load(config_yaml)


@pytest.fixture
def gxit_startup_config(galaxy_virtualenv, gxit_config):
gxit_config['gravity']['virtualenv'] = galaxy_virtualenv
return gxit_config


@pytest.fixture(scope="session")
def galaxy_virtualenv(galaxy_root_dir):
virtual_env_dir = str(TEST_DIR / "galaxy_venv")
Expand Down
55 changes: 40 additions & 15 deletions tests/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
def test_cmd_register(state_dir, galaxy_yml):
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'register', str(galaxy_yml)])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert 'Registered galaxy config:' in result.output


def test_cmd_deregister(state_dir, galaxy_yml):
test_cmd_register(state_dir, galaxy_yml)
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'deregister', str(galaxy_yml)])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert 'Deregistered config:' in result.output


Expand All @@ -38,11 +38,22 @@ def wait_for_startup(state_dir, free_port, prefix="/"):
return startup_logs


def wait_for_gxit_proxy(state_dir):
startup_logs = ""
with open(state_dir / "log" / 'gx-it-proxy.log') as fh:
for _ in range(STARTUP_TIMEOUT * 4):
startup_logs = fh.read()
if 'Listening' in startup_logs:
return True
time.sleep(0.25)
return startup_logs


def start_instance(state_dir, free_port):
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'start'])
assert re.search(r"gunicorn\s*STARTING", result.output)
assert result.exit_code == 0
assert result.exit_code == 0, result.output
startup_done = wait_for_startup(state_dir, free_port)
assert startup_done is True, f"Startup failed. Application startup logs:\n {startup_done}"

Expand All @@ -51,29 +62,43 @@ def test_cmd_start(state_dir, galaxy_yml, startup_config, free_port):
galaxy_yml.write(json.dumps(startup_config))
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'register', str(galaxy_yml)])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'update'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
start_instance(state_dir, free_port)
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'stop'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert "All processes stopped, supervisord will exit" in result.output


def test_cmd_start_with_gxit(state_dir, galaxy_yml, gxit_startup_config, free_port):
galaxy_yml.write(json.dumps(gxit_startup_config))
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'register', str(galaxy_yml)])
assert result.exit_code == 0, result.output
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'update'])
assert result.exit_code == 0, result.output
start_instance(state_dir, free_port)
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'status'])
assert result.exit_code == 0, result.output
startup_done = wait_for_gxit_proxy(state_dir)
assert startup_done is True, f"gx-it-proxy startup failed. gx-it-proxy startup logs:\n {startup_done}"


def test_cmd_restart_with_update(state_dir, galaxy_yml, startup_config, free_port):
galaxy_yml.write(json.dumps(startup_config))
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'register', str(galaxy_yml)])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'update'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
start_instance(state_dir, free_port)
# change prefix
prefix = '/galaxypf/'
startup_config['galaxy']['galaxy_url_prefix'] = prefix
galaxy_yml.write(json.dumps(startup_config))
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'restart'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
startup_done = wait_for_startup(state_dir=state_dir, free_port=free_port, prefix=prefix)
assert startup_done is True, f"Startup failed. Application startup logs:\n {startup_done}"

Expand All @@ -82,7 +107,7 @@ def test_cmd_show(state_dir, galaxy_yml):
test_cmd_register(state_dir, galaxy_yml)
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'show', str(galaxy_yml)])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
details = safe_load(result.output)
assert details['config_type'] == 'galaxy'

Expand All @@ -97,7 +122,7 @@ def test_cmd_show_config_does_not_exist(state_dir, galaxy_yml):
assert f'To register this config file run "galaxyctl register {str(galaxy_yml)}"' in result.output
# register the sample file, but ask for galaxy.yml
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'register', str(galaxy_yml + '.sample')])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'show', str(galaxy_yml)])
assert result.exit_code == 1
assert f"{str(galaxy_yml)} is not a registered config file." in result.output
Expand All @@ -108,21 +133,21 @@ def test_cmd_show_config_does_not_exist(state_dir, galaxy_yml):
def test_cmd_instances(state_dir, galaxy_yml):
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'instances'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert not result.output
test_cmd_register(state_dir, galaxy_yml)
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'instances'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert "_default_" in result.output


def test_cmd_configs(state_dir, galaxy_yml):
runner = CliRunner()
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'configs'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert 'No config files registered' in result.output
test_cmd_register(state_dir, galaxy_yml)
result = runner.invoke(galaxyctl, ['--state-dir', state_dir, 'configs'])
assert result.exit_code == 0
assert result.exit_code == 0, result.output
assert result.output.startswith("TYPE")
assert str(galaxy_yml) in result.output
27 changes: 27 additions & 0 deletions tests/test_process_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ def test_update(galaxy_yml, default_config_manager):
pm.update()


def test_update_force(galaxy_yml, default_config_manager):
test_update(galaxy_yml, default_config_manager)
instance_conf_dir = Path(default_config_manager.state_dir) / 'supervisor' / 'supervisord.conf.d' / '_default_.d'
gunicorn_conf_path = instance_conf_dir / "galaxy_gunicorn_gunicorn.conf"
assert gunicorn_conf_path.exists()
update_time = gunicorn_conf_path.stat().st_mtime
with process_manager.process_manager(state_dir=default_config_manager.state_dir) as pm:
pm.update()
assert gunicorn_conf_path.stat().st_mtime == update_time
with process_manager.process_manager(state_dir=default_config_manager.state_dir) as pm:
pm.update(force=True)
assert gunicorn_conf_path.stat().st_mtime != update_time


@pytest.mark.parametrize('job_conf', [[JOB_CONF_XML_DYNAMIC_HANDLERS]], indirect=True)
def test_dynamic_handlers(default_config_manager, galaxy_yml, job_conf):
galaxy_yml.write(DYNAMIC_HANDLER_CONFIG)
Expand Down Expand Up @@ -97,3 +111,16 @@ def test_static_handlers(default_config_manager, galaxy_yml, job_conf):
handler1_config_path = instance_conf_dir / 'galaxy_standalone_handler1.conf'
assert handler1_config_path.exists()
assert 'galaxy.yml --server-name=handler1 --pid-file=' in handler1_config_path.open().read()


def test_gxit_handler(default_config_manager, galaxy_yml, gxit_config):
galaxy_yml.write(json.dumps(gxit_config))
default_config_manager.add([str(galaxy_yml)])
with process_manager.process_manager(state_dir=default_config_manager.state_dir) as pm:
pm.update()
instance_conf_dir = Path(default_config_manager.state_dir) / 'supervisor' / 'supervisord.conf.d' / '_default_.d'
gxit_config_path = instance_conf_dir / 'galaxy_gx-it-proxy_gx-it-proxy.conf'
assert gxit_config_path.exists()
gxit_port = gxit_config["gravity"]["gx_it_proxy"]["port"]
sessions = "database/interactivetools_map.sqlite"
assert f'npx gx-it-proxy --ip localhost --port {gxit_port} --sessions {sessions}' in gxit_config_path.read_text()

0 comments on commit 539fd35

Please sign in to comment.