Repository: DataBiosphere/toil

Issue:
Implement `/engine/v1/status` health check endpoint in `toil server`
AGC expects a health check endpoint that it can ping to see if its WES server is alive, and it defaults to `/engine/v1/status`. WES doesn't really have a good alternative in it (there's `service-info` but that's a bit more expensive to poll constantly), and AGC doesn't currently have a good way to pick an alternative endpoint based on engine type.

The simplest thing to do is to implement this nonstandard extension and always return 200.


Discussion:

Resolving patch:
diff --git a/src/toil/server/app.py b/src/toil/server/app.py
--- a/src/toil/server/app.py
+++ b/src/toil/server/app.py
@@ -85,9 +85,14 @@ def create_app(args: argparse.Namespace) -> "connexion.FlaskApp":
 
     # add custom endpoints
     if isinstance(backend, ToilBackend):
+        # We extend the WES API to allow presenting log data
         base_url = "/toil/wes/v1"
         flask_app.app.add_url_rule(f"{base_url}/logs/<run_id>/stdout", view_func=backend.get_stdout)
         flask_app.app.add_url_rule(f"{base_url}/logs/<run_id>/stderr", view_func=backend.get_stderr)
+        # To be a well-behaved AGC engine we can implement the default status check endpoint
+        flask_app.app.add_url_rule("/engine/v1/status", view_func=backend.get_health)
+        # And we can provide lost humans some information on what they are looking at
+        flask_app.app.add_url_rule("/", view_func=backend.get_homepage)
 
     return flask_app
 
diff --git a/src/toil/server/wes/abstract_backend.py b/src/toil/server/wes/abstract_backend.py
--- a/src/toil/server/wes/abstract_backend.py
+++ b/src/toil/server/wes/abstract_backend.py
@@ -9,7 +9,7 @@
 from urllib.parse import urldefrag
 
 import connexion  # type: ignore
-from werkzeug.utils import secure_filename  # type: ignore
+from werkzeug.utils import secure_filename
 
 logger = logging.getLogger(__name__)
 
diff --git a/src/toil/server/wes/toil_backend.py b/src/toil/server/wes/toil_backend.py
--- a/src/toil/server/wes/toil_backend.py
+++ b/src/toil/server/wes/toil_backend.py
@@ -21,6 +21,9 @@
 from typing import Optional, List, Dict, Any, overload, Generator, Tuple
 
 from flask import send_from_directory
+from werkzeug.utils import redirect
+from werkzeug.wrappers.response import Response
+
 
 from toil.server.utils import safe_read_file, safe_write_file
 from toil.server.wes.abstract_backend import (WESBackend,
@@ -346,3 +349,20 @@ def get_stderr(self, run_id: str) -> Any:
         """
         self._get_run(run_id, should_exists=True)
         return send_from_directory(self.work_dir, os.path.join(run_id, "stderr"), mimetype="text/plain")
+
+    @handle_errors
+    def get_health(self) -> Response:
+        """
+        Return successfully if the server is healthy.
+        """
+        return Response("OK", mimetype="text/plain")
+
+    @handle_errors
+    def get_homepage(self) -> Response:
+        """
+        Provide a sensible result for / other than 404.
+        """
+        # For now just go to the service info endpoint
+        return redirect('ga4gh/wes/v1/service-info', code=302)
+
+


Test patch:
diff --git a/src/toil/test/server/serverTest.py b/src/toil/test/server/serverTest.py
--- a/src/toil/test/server/serverTest.py
+++ b/src/toil/test/server/serverTest.py
@@ -56,6 +56,18 @@ def setUp(self) -> None:
     def tearDown(self) -> None:
         super(ToilWESServerTest, self).tearDown()
 
+    def test_home(self) -> None:
+        """ Test the homepage endpoint."""
+        with self.app.test_client() as client:
+            rv = client.get("/")
+        self.assertEqual(rv.status_code, 302)
+
+    def test_health(self) -> None:
+        """ Test the health check endpoint."""
+        with self.app.test_client() as client:
+            rv = client.get("/engine/v1/status")
+        self.assertEqual(rv.status_code, 200)
+
     def test_get_service_info(self) -> None:
         """ Test the GET /service-info endpoint."""
         with self.app.test_client() as client: