""" This test file tests bootstrap.py ability to - error verbosely for old ubuntu - error verbosely for no systemd - start and provide a progress page web server """ import concurrent.futures import os import subprocess import time GIT_REPO_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) BASE_IMAGE = os.getenv("BASE_IMAGE", "ubuntu:20.04") def _stop_container(): """ Stops a container if its already running. """ subprocess.run( ["docker", "rm", "--force", "test-bootstrap"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def _run_bootstrap_in_container(image, complete_setup=True): """ 1. (Re-)starts a container named test-bootstrap based on image, mounting local git repo and exposing port 8080 to the containers port 80. 2. Installs python3, systemd, git, and curl in container 3. Runs bootstrap/bootstrap.py in container to install the mounted git repo's tljh package in --editable mode. """ _stop_container() # Start a detached container subprocess.check_output( [ "docker", "run", "--env=DEBIAN_FRONTEND=noninteractive", "--env=TLJH_BOOTSTRAP_DEV=yes", "--env=TLJH_BOOTSTRAP_PIP_SPEC=/srv/src", f"--volume={GIT_REPO_PATH}:/srv/src", "--publish=8080:80", "--detach", "--name=test-bootstrap", image, "bash", "-c", "sleep 300s", ] ) run = ["docker", "exec", "-i", "test-bootstrap"] subprocess.check_output(run + ["apt-get", "update"]) subprocess.check_output(run + ["apt-get", "install", "--yes", "python3"]) if complete_setup: subprocess.check_output( run + ["apt-get", "install", "--yes", "systemd", "git", "curl"] ) run_bootstrap = run + [ "python3", "/srv/src/bootstrap/bootstrap.py", "--show-progress-page", ] # Run bootstrap script inside detached container, return the output return subprocess.run( run_bootstrap, text=True, capture_output=True, ) def test_ubuntu_too_old(): """ Error with a useful message when running in older Ubuntu """ output = _run_bootstrap_in_container("ubuntu:18.04", False) _stop_container() assert output.stdout == "The Littlest JupyterHub requires Ubuntu 20.04 or higher\n" assert output.returncode == 1 def test_no_systemd(): output = _run_bootstrap_in_container("ubuntu:22.04", False) assert "Systemd is required to run TLJH" in output.stdout assert output.returncode == 1 def _wait_for_progress_page_response(expected_status_code, timeout): start = time.time() while time.time() - start < timeout: try: resp = subprocess.check_output( [ "curl", "--include", "http://localhost:8080/index.html", ], text=True, stderr=subprocess.DEVNULL, ) if "HTTP/1.0 200 OK" in resp: return True except Exception: pass time.sleep(1) return False def test_show_progress_page(): with concurrent.futures.ThreadPoolExecutor() as executor: run_bootstrap_job = executor.submit(_run_bootstrap_in_container, BASE_IMAGE) # Check that the bootstrap script started the web server reporting # progress successfully responded. success = _wait_for_progress_page_response( expected_status_code=200, timeout=180 ) if success: # Let's terminate the test here and save a minute or so in test # executation time, because we can know that the will be stopped # successfully in other tests as otherwise traefik won't be able to # start and use the same port for example. return # Now await an expected failure to startup JupyterHub by tljh.installer, # which should have taken over the work started by the bootstrap script. # # This failure is expected to occur in # tljh.installer.ensure_jupyterhub_service calling systemd.reload_daemon # like this: # # > System has not been booted with systemd as init system (PID 1). # > Can't operate. # output = run_bootstrap_job.result() print(output.stdout) print(output.stderr) # At this point we should be able to see that tljh.installer # intentionally stopped the web server reporting progress as the port # were about to become needed by Traefik. assert "Progress page server stopped successfully." in output.stdout assert success