diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/authentication.rst b/docs/source/authentication.rst new file mode 100644 index 0000000..29d1c7d --- /dev/null +++ b/docs/source/authentication.rst @@ -0,0 +1,36 @@ +Authentication +============== + +A single Keycloak client is used for the authentication with both JupyterHub and FirecREST. +As the user logs in JupyterHub, an access token and refresh token are obtained directly from Keycloak. +The access token is used for the authentication with both the hub and FirecREST. +Since access tokens are temporary, before doing any operation requiring authentication (such as FirecREST calls), they are refreshed using the refresh token, which have a longer lifetime. + +For the Hub, the refreshing of the access token is done by the authenticator's `refresh_user method `_, which must be defined in the ``GenericOAuthenticatorCSCS`` class. +It's run time to time as needed depending on the ``c.Authenticator.auth_refresh_age`` parameter. + +That's different for the spawner. Any time a firecrest function is called, a new client is created and used to run the command. +Now, any time a client is created, a new access token is requested with the refresh token. +That's done in the spawner itself and it's independent of the credential refreshing for the hub (except that the refresh token is obtained from the *authentication state* of the Hub - see section :ref:`auth-state`) + +Right now, creating a new client always makes a request to refresh the access token, but the idea is to check if the current access token is expired or not, to reduce the number of refreshing requests. + +.. _auth-state: + +Enabling the authentication state +--------------------------------- + +The access and refresh tokens are kept stored in JupyterHub's `authentication state `_ dictionary. +From there, they are fetched by the spawner and passed to the FirecREST clients which submit, poll and cancel the jobs that run the JupyterLab servers. +By default, JupyterHub doesn't store the authentication state. +That must be enabled in the configuration + +.. code-block:: Python + + c.Authenticator.enable_auth_state = True + +which in turns requires setting ``c.CryptKeeper.keys`` in the JupyterHub and the environment variable ``JUPYTERHUB_CRYPT_KEY`` in the single-user side. + +Once that's done, there's only left to add to the configuration the settings for the authentication with keycloak and extend the ``oauthenticator``'s ``GenericOAuthenticator`` class to provide the ``refresh_user`` method, which takes care of refreshing the access token. + +The access and refresh tokens are kept stored in JupyterHub's "authentication state" \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..6bc2a26 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,34 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + + +os.environ['FIRECREST_URL'] = 'dummy' + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'FirecRESTSpawner' +copyright = '2024, CSCS Swiss National Supercomputing Center' +author = 'CSCS Swiss National Supercomputing Center' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ['sphinx.ext.autodoc'] + +templates_path = ['_templates'] +exclude_patterns = [] + +language = 'Python' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst new file mode 100644 index 0000000..ea1de6d --- /dev/null +++ b/docs/source/deployment.rst @@ -0,0 +1,70 @@ +Deployment +========== + +The deployment has two sides: + +- The hub and the proxy: + Users connect to the hub (JupyterHub) and launch JupyterLab servers via FirecREST that will run in compute nodes of HPC clusters. + The proxy routes the communication from the browser to the hub or to the JupyterLab servers. + Besides access to the internet, no special requirements are necessary for where to run the the hub and the proxy. + +- The JupyterLab user servers launched in compute nodes of HPC clusters: + These JupyterLab servers (also know as single-user servers within JupyterHub) come and go as users spawn or stop them. + An installation of JupyterLab and other packages must be provided in the HPC cluster. + That could be a native installation somewhere in the system or provided as a container image. + This doesn't need FirecREST and it's not concerned either with JupyterHub's configuration nor the FirecREST's client credentials + +Reference deployment at CSCS +---------------------------- + +At CSCS we run JupyterHub in Kubernetes and from there JupyterLab notebooks are launched via FirecREST ito different HPC clusters. +Each cluster has its own deployment, i.e its own JupyterHub server. + +We deploy the hub and proxy using the `f7t4jhub `_ helm chart that we have prepared. +The chart has been written with CSCS' use case in mind, but we would be glad to make it more general if it were used in other sites. +You can give it a look in the `spawner's rpository `_ or search it from the command line with helm: + +.. code-block:: Shell + + $> helm repo add f7t4jhub https://eth-cscs.github.io/firecrestspawner + $> helm repo update + $> helm search repo f7t4jhub/f7t4jhub --versions + NAME CHART VERSION APP VERSION DESCRIPTION + f7t4jhub/f7t4jhub 0.6.0 4.1.5 A Helm chart to Deploy JupyterHub with the Fire... + f7t4jhub/f7t4jhub 0.5.2 4.1.5 A Helm chart to Deploy JupyterHub with the Fire... + f7t4jhub/f7t4jhub 0.5.1 4.1.5 A Helm chart to Deploy JupyterHub with the Fire... + f7t4jhub/f7t4jhub 0.5.0 4.1.5 A Helm chart to Deploy JupyterHub with the Fire... + f7t4jhub/f7t4jhub 0.3.0 4.1.5 A Helm chart to Deploy JupyterHub with the Fire... + + +In our deployments, both the hub and proxy run in their own pods, as that makes possible restarting the hub if needed (to apply a new configuration, for instance) without affecting users that have JupyterLab servers running. +As proxy, we use `configurable-http `_ from the container image ``quay.io/jupyterhub/configurable-http-proxy:4.6.1``. +For the hub, we use the custom container image ``ghcr.io/eth-cscs/f7t4jhub:4.1.5`` which contains JupyterHub and the FirecRESTSpawner. +The corresponding Dockerfile can be found `here `_. +JupyterHub's configuration and FirecREST's client credentials are passed via a Kubernetes ConfigMap and Secret, respectively. + +.. image:: images/cscs-deployment.png + :alt: Company Logo + :width: 500px + :align: center + +At CSCS, the Keycloak client's IDs and secrets to login in JupyterHub are stored in `Vault `_. +They can be accessed in our kubernetes deployment via a set of secrets: + +- The `vault-approle-secret` kubernetes `Secret`, which contains the credentials to access Vault. + This secret is not part of the helm chart. It must be created manually for the namespace where the chart will be deployed. + +- A `SecretStore `_, which interacts with the `vault-approle-secret` secret. + +- An `ExternalSecret `_ which in turns interacts with the secret store. + The deployment access the Keycloak client's IDs and secrets from this external secret. + +This part related to Vault is optional and can be disabled in the chart's ``values.yaml``. + +Another item of the chart worth a remark is the `ConfigMap` that provides the `JupyterHub configuration `_. +The configuration has a lot of parameters that can be tweaked. +However, in practice, only a handful have to be modified from one deployment to another. +Because of that, templating only those parameters should be enough to produce a generic chart that can be used for all deployments by only changing corresponding values in the `values.yaml`. + +In our deployments, the required changes are mostly related to the authentication settings and the batch script used by the spawner to submit the JupyterLab servers since the slurm settings may change depending on the cluster. +All parameters related to JupyterHub's configuration are set under `config` in the `values.yaml`. \ No newline at end of file diff --git a/docs/source/images/cscs-deployment.png b/docs/source/images/cscs-deployment.png new file mode 100644 index 0000000..dfc9464 Binary files /dev/null and b/docs/source/images/cscs-deployment.png differ diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..217f119 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,23 @@ +.. FirecRESTSpawner documentation master file, created by + sphinx-quickstart on Thu Jun 13 10:08:06 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to FirecRESTSpawner's documentation! +============================================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + authentication + deployment + reference + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/reference.rst b/docs/source/reference.rst new file mode 100644 index 0000000..a8a8d7f --- /dev/null +++ b/docs/source/reference.rst @@ -0,0 +1,9 @@ +Reference +========= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + reference_spawners + reference_singleuser diff --git a/docs/source/reference_singleuser.rst b/docs/source/reference_singleuser.rst new file mode 100644 index 0000000..52f14d3 --- /dev/null +++ b/docs/source/reference_singleuser.rst @@ -0,0 +1,4 @@ +Single-user server +================== + +The library also provides an asynchronous API for the client: diff --git a/docs/source/reference_spawners.rst b/docs/source/reference_spawners.rst new file mode 100644 index 0000000..92476fc --- /dev/null +++ b/docs/source/reference_spawners.rst @@ -0,0 +1,19 @@ +Spawners +======== + +The library also provides an asynchronous API for the client: + +The ``FirecRESTSpawnerBase`` class +********************************** +.. autoclass:: firecrestspawner.spawner.FirecRESTSpawnerBase + :members: + :undoc-members: + :show-inheritance: + + +The ``SlurmSpawner`` class +**************************** +.. autoclass:: firecrestspawner.spawner.SlurmSpawner + :members: + :undoc-members: + :show-inheritance: