Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support high-availability setups for the interactive tools proxy #18481

Merged
merged 24 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b54bf35
Support using a table in any database supported by SQLAlchemy as mapp…
kysrpex Jun 28, 2024
bf6b6ed
Fix bug when using a mapping for interactive tools from a SQLAlchemy …
kysrpex Jul 4, 2024
967b591
Merge branch 'refs/heads/dev' into interactivetoolssqlalchemy
kysrpex Jul 4, 2024
d85e1b5
Replace raw SQL queries with SQLAlchemy Core SQL expressions
kysrpex Jul 17, 2024
ce6d0f7
Configure SQLAlchemy interactive tool maps via new `interactivetools_…
kysrpex Jul 18, 2024
647fd4c
Revert changes to description for setting `sessions` in galaxy.yml.sa…
kysrpex Jul 18, 2024
678bea1
Forbid setting `interactivetools_map_sqlalchemy` from matching `datab…
kysrpex Jul 18, 2024
dddf5ba
Add note in interactive tools docs page informing about `interactivet…
kysrpex Jul 18, 2024
6be9b60
workaround `mypy` error on statement using `sqlalchemy.select`
kysrpex Jul 22, 2024
e7dbe42
Create `gxitproxy` database table when using interactive tools for th…
kysrpex Jul 23, 2024
c1fd2b5
Merge branch 'dev' into interactivetoolssqlalchemy
kysrpex Jul 23, 2024
b6ad695
Add `sqlalchemy` to config package requirements
kysrpex Aug 2, 2024
5e8ca55
Test that Galaxy raises `ConfigurationError` when setting `interactiv…
kysrpex Aug 2, 2024
26fd210
Set `interactivetools_map` to `None` when `interactivetools_map_sqlal…
kysrpex Aug 9, 2024
ffa8ea4
Fix typos in descriptions of `interactivetools_map` and `interactivet…
kysrpex Aug 9, 2024
018e541
Mention that `interactivetools_map_sqlalchemy` overrides `interactive…
kysrpex Aug 9, 2024
e040750
Refactor variable name `query` to `stmt` for calls to `select()`, `in…
kysrpex Aug 9, 2024
8305016
Access individual table columns via `__getattr__` rather than `__geti…
kysrpex Aug 9, 2024
6fc0d46
Restore original error handling for `InteractiveToolPropagatorSQLAlch…
kysrpex Aug 9, 2024
4e6688c
Workaround to have original error handling for `get()` and pass mypy …
kysrpex Aug 9, 2024
0b6ad17
Remove dead code `InteractiveToolPropagatorSQLAlchemy.get()`
kysrpex Sep 3, 2024
9ee6636
Rename `interactivetools_map_sqlalchemy` to `interactivetoolsproxy_map`
kysrpex Sep 3, 2024
5c0b5d6
Use `urlparse` to compare `interactivetoolsproxy_map` with `database_…
kysrpex Sep 3, 2024
7510372
Revert "Add `sqlalchemy` to config package requirements"
kysrpex Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion doc/source/admin/galaxy_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2112,13 +2112,34 @@
~~~~~~~~~~~~~~~~~~~~~~~~

:Description:
Map for interactivetool proxy.
Map for the interactivetool proxy. Mappings are stored in a SQLite
database file located on this path. As an alternative, you may
also store them in any other RDBMS supported by SQLAlchemy using
the option ``interactivetools_map_sqlalchemy``, which overrides
this one.
The value of this option will be resolved with respect to
<data_dir>.
:Default: ``interactivetools_map.sqlite``
:Type: str


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``interactivetools_map_sqlalchemy``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:Description:
Use a database supported by SQLAlchemy as map for the
interactivetool proxy. When this option is set, the value of
``interactivetools_map`` is ignored. The value of this option must
be a `SQLAlchemy database URL
<https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls>`_.
Mappings are written to the table "gxitproxy" within the database.
This value cannot match ``database_connection`` nor
``install_database_connection``.
:Default: ``None``
:Type: str


~~~~~~~~~~~~~~~~~~~~~~~~~~~
``interactivetools_prefix``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
13 changes: 13 additions & 0 deletions doc/source/admin/special_topics/interactivetools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ The ``gx-it-proxy`` config relates to an important service in the InteractiveToo
proxy. ``gx-it-proxy`` runs as a separate process listening at port 4002 (by default). HTTP requests are decoded based on
the URL and headers, then somewhat massaged, and finally forwarded to the correct entry point port of the target InteractiveTool.

.. note::

Entry point mappings used by the proxy are stored on a SQLite database file located at ``interactivetools_map``. In
`some situations <https://www.sqlite.org/whentouse.html#situations_where_a_client_server_rdbms_may_work_better>`_,
SQLite may not be the best choice. A common case is a high-availability production setup, meaning that multiple
copies of Galaxy are running on different servers behind a load balancer.

For these situations, there exists an optional |configuration option interactivetools_map_sqlalchemy|_ that allows
using any database supported by SQLAlchemy (it overrides ``interactivetools_map``).

.. |configuration option interactivetools_map_sqlalchemy| replace:: configuration option ``interactivetools_map_sqlalchemy``
.. _configuration option interactivetools_map_sqlalchemy: ../config.html#interactivetools-map-sqlalchemy

.. note::

A previous config option ``interactivetools_shorten_url`` was removed in commit `#73100de <https://github.com/galaxyproject/galaxy/pull/16795/commits/73100de17149ca3486c83b8c6ded74987c68a836>`_
Expand Down
40 changes: 36 additions & 4 deletions lib/galaxy/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from urllib.parse import urlparse

import yaml
from sqlalchemy.engine import make_url as parse_sqlalchemy_url

from galaxy.config.schema import AppSchema
from galaxy.exceptions import ConfigurationError
Expand Down Expand Up @@ -1103,10 +1104,39 @@ def _process_config(self, kwargs: Dict[str, Any]) -> None:
self.proxy_session_map = self.dynamic_proxy_session_map
self.manage_dynamic_proxy = self.dynamic_proxy_manage # Set to false if being launched externally

# InteractiveTools propagator mapping file
self.interactivetools_map = self._in_root_dir(
kwargs.get("interactivetools_map", self._in_data_dir("interactivetools_map.sqlite"))
)
# InteractiveTools propagator mapping
if self.interactivetools_map_sqlalchemy is None:
self.interactivetools_map = "sqlite:///" + self._in_root_dir(
kwargs.get("interactivetools_map", self._in_data_dir("interactivetools_map.sqlite"))
)
else:
self.interactivetools_map = None # overridden by `self.interactivetools_map_sqlalchemy`

# ensure the database URL for the SQLAlchemy map does not match that of a Galaxy DB
urls = {
setting: parse_sqlalchemy_url(value)
for setting, value in (
("interactivetools_map_sqlalchemy", self.interactivetools_map_sqlalchemy),
("database_connection", self.database_connection),
("install_database_connection", self.install_database_connection),
)
if value is not None
}

def is_in_conflict(url1, url2):
return all((url1.host == url2.host, url1.port == url2.port, url1.database == url2.database))

conflicting_settings = {
setting
for setting, url in tuple(urls.items())[1:] # exclude "interactivetools_map_sqlalchemy"
if is_in_conflict(url, list(urls.values())[0]) # compare with "interactivetools_map_sqlalchemy"
}

if conflicting_settings:
raise ConfigurationError(
f"Option `{tuple(urls)[0]}` cannot take the same value as: %s"
% ", ".join(f"`{setting}`" for setting in conflicting_settings)
)

# Compliance/Policy variables
self.redact_username_during_deletion = False
Expand Down Expand Up @@ -1227,6 +1257,8 @@ def try_parsing(value, name):

try_parsing(self.database_connection, "database_connection")
try_parsing(self.install_database_connection, "install_database_connection")
if self.interactivetools_map_sqlalchemy is not None:
try_parsing(self.interactivetools_map_sqlalchemy, "interactivetools_map_sqlalchemy")
try_parsing(self.amqp_internal_connection, "amqp_internal_connection")

def _configure_dataset_storage(self):
Expand Down
18 changes: 16 additions & 2 deletions lib/galaxy/config/sample/galaxy.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ gravity:

# Routes file to monitor.
# Should be set to the same path as ``interactivetools_map`` in the ``galaxy:`` section. This is ignored if
# ``interactivetools_map is set``.
# ``interactivetools_map is set.
# sessions: database/interactivetools_map.sqlite

# Include verbose messages in gx-it-proxy
Expand Down Expand Up @@ -1348,11 +1348,25 @@ galaxy:
# subdomain. Defaults to "/".
#interactivetools_base_path: /

# Map for interactivetool proxy.
# Map for the interactivetool proxy. Mappings are stored in a SQLite
# database file located on this path. As an alternative, you may also
# store them in any other RDBMS supported by SQLAlchemy using the
# option ``interactivetools_map_sqlalchemy``, which overrides this
# one.
# The value of this option will be resolved with respect to
# <data_dir>.
#interactivetools_map: interactivetools_map.sqlite

# Use a database supported by SQLAlchemy as map for the
# interactivetool proxy. When this option is set, the value of
# ``interactivetools_map`` is ignored. The value of this option must
# be a `SQLAlchemy database URL
# <https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls>`_.
# Mappings are written to the table "gxitproxy" within the database.
# This value cannot match ``database_connection`` nor
# ``install_database_connection``.
#interactivetools_map_sqlalchemy: null

# Prefix to use in the formation of the subdomain or path for
# interactive tools
#interactivetools_prefix: interactivetool
Expand Down
16 changes: 15 additions & 1 deletion lib/galaxy/config/schemas/config_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1520,7 +1520,21 @@ mapping:
default: interactivetools_map.sqlite
path_resolves_to: data_dir
desc: |
jdavcs marked this conversation as resolved.
Show resolved Hide resolved
Map for interactivetool proxy.
Map for the interactivetool proxy. Mappings are stored in a SQLite database file
located on this path. As an alternative, you may also store them in any other RDBMS
supported by SQLAlchemy using the option ``interactivetools_map_sqlalchemy``, which
overrides this one.

interactivetools_map_sqlalchemy:
type: str
required: false
desc: |
Use a database supported by SQLAlchemy as map for the interactivetool proxy.
When this option is set, the value of ``interactivetools_map`` is ignored. The
value of this option must be a
`SQLAlchemy database URL <https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls>`_.
Mappings are written to the table "gxitproxy" within the database. This value cannot match
``database_connection`` nor ``install_database_connection``.

interactivetools_prefix:
type: str
Expand Down
Loading
Loading