From f55c0d77dc0ae511505da32a7640adfe8b057ca6 Mon Sep 17 00:00:00 2001 From: Konstantinos Andriopoulos <49658958+kandrio98@users.noreply.github.com> Date: Thu, 20 Aug 2020 13:25:22 +0300 Subject: [PATCH] tensorboard web-app: Create Tensorboard web-app backend (kubeflow/kubeflow#5180) * Create Tensorboard web-app backend Create the code for the Tensorboard web-app backend which includes routes for GET, POST and DELETE requests. The backend is created with Python/Flask, so it also uses the common code from 'kubeflow.kubeflow.crud_backend'. * Add 'get_age(k8s_object)' function to 'crud_backend' common code It would be useful for all web apps of the 'crud-web-apps' folder to return age information to their frontends. As a result, 'get_age(k8s_object)' was added to the common code, so that all web apps can use it. --- .../kubeflow/kubeflow/crud_backend/helpers.py | 15 ++++++++ .../tensorboards/backend/Makefile | 9 +++++ .../tensorboards/backend/app/__init__.py | 17 +++++++++ .../backend/app/routes/__init__.py | 5 +++ .../tensorboards/backend/app/routes/delete.py | 28 ++++++++++++++ .../tensorboards/backend/app/routes/get.py | 20 ++++++++++ .../tensorboards/backend/app/routes/post.py | 38 +++++++++++++++++++ .../tensorboards/backend/app/utils.py | 31 +++++++++++++++ .../tensorboards/backend/entrypoint.py | 20 ++++++++++ 9 files changed, 183 insertions(+) create mode 100644 components/crud-web-apps/tensorboards/backend/Makefile create mode 100644 components/crud-web-apps/tensorboards/backend/app/__init__.py create mode 100644 components/crud-web-apps/tensorboards/backend/app/routes/__init__.py create mode 100644 components/crud-web-apps/tensorboards/backend/app/routes/delete.py create mode 100644 components/crud-web-apps/tensorboards/backend/app/routes/get.py create mode 100644 components/crud-web-apps/tensorboards/backend/app/routes/post.py create mode 100644 components/crud-web-apps/tensorboards/backend/app/utils.py create mode 100644 components/crud-web-apps/tensorboards/backend/entrypoint.py diff --git a/components/crud-web-apps/common/backend/kubeflow/kubeflow/crud_backend/helpers.py b/components/crud-web-apps/common/backend/kubeflow/kubeflow/crud_backend/helpers.py index a4d5802f..95db6502 100644 --- a/components/crud-web-apps/common/backend/kubeflow/kubeflow/crud_backend/helpers.py +++ b/components/crud-web-apps/common/backend/kubeflow/kubeflow/crud_backend/helpers.py @@ -101,3 +101,18 @@ def get_uptime(then): age = str(mins) + " mins" return age + " ago" + + +def get_age(k8s_object): + """ + k8s_object: k8s custom resource | dictionary + + Return a dictionary that contains the creationTimestamp (timestamp) of the + given k8s object and the amount of time that has passed from that timestamp + (uptime). + """ + return { + "uptime": get_uptime( + k8s_object["metadata"]["creationTimestamp"]), + "timestamp": k8s_object["metadata"]["creationTimestamp"], + } diff --git a/components/crud-web-apps/tensorboards/backend/Makefile b/components/crud-web-apps/tensorboards/backend/Makefile new file mode 100644 index 00000000..b3d6ed50 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/Makefile @@ -0,0 +1,9 @@ +install-deps: + cd ../../common/backend && pip install -e . + +run: + gunicorn -w 3 --bind 0.0.0.0:5000 --access-logfile - entrypoint:_app + +run-dev: + BACKEND_MODE=dev \ + python entrypoint.py diff --git a/components/crud-web-apps/tensorboards/backend/app/__init__.py b/components/crud-web-apps/tensorboards/backend/app/__init__.py new file mode 100644 index 00000000..2543b8f1 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/__init__.py @@ -0,0 +1,17 @@ +import kubeflow.kubeflow.crud_backend as base +from kubeflow.kubeflow.crud_backend import config, logging + +from .routes import bp as routes_bp + +log = logging.getLogger(__name__) + + +def create_app( + name=__name__, static_folder="static", config_class=config.Config +): + app = base.create_app(name, static_folder, config_class) + + # Register the app's blueprints + app.register_blueprint(routes_bp) + + return app diff --git a/components/crud-web-apps/tensorboards/backend/app/routes/__init__.py b/components/crud-web-apps/tensorboards/backend/app/routes/__init__.py new file mode 100644 index 00000000..358169bf --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/routes/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint("base_routes", __name__) + +from . import get, post, delete # noqa E402, F401 diff --git a/components/crud-web-apps/tensorboards/backend/app/routes/delete.py b/components/crud-web-apps/tensorboards/backend/app/routes/delete.py new file mode 100644 index 00000000..b89f970d --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/routes/delete.py @@ -0,0 +1,28 @@ +from kubeflow.kubeflow.crud_backend import api, logging + +from . import bp + +log = logging.getLogger(__name__) + + +@bp.route( + "/api/namespaces//tensorboards/", + methods=["DELETE"], +) +def delete_tensorboard(tensorboard, namespace): + + log.info("About to delete Tensorboard %s/%s", tensorboard, namespace) + api.delete_custom_rsrc( + "tensorboard.kubeflow.org", + "v1alpha1", + "tensorboards", + tensorboard, + namespace, + ) + log.info( + "DELETE request was sent to the API Server for Tensorboard: %s/%s", + tensorboard, + namespace, + ) + + return api.success_response("message", "Tensorboard deleted successfully.") diff --git a/components/crud-web-apps/tensorboards/backend/app/routes/get.py b/components/crud-web-apps/tensorboards/backend/app/routes/get.py new file mode 100644 index 00000000..083f0398 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/routes/get.py @@ -0,0 +1,20 @@ +from kubeflow.kubeflow.crud_backend import api, logging + +from .. import utils +from . import bp + +log = logging.getLogger(__name__) + + +@bp.route("/api/namespaces//tensorboards") +def get_tensorboards(namespace): + + tensorboards = api.list_custom_rsrc( + "tensorboard.kubeflow.org", "v1alpha1", "tensorboards", namespace + ) + content = [ + utils.parse_tensorboard(tensorboard) + for tensorboard in tensorboards["items"] + ] + + return api.success_response("tensorboards", content) diff --git a/components/crud-web-apps/tensorboards/backend/app/routes/post.py b/components/crud-web-apps/tensorboards/backend/app/routes/post.py new file mode 100644 index 00000000..8a637888 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/routes/post.py @@ -0,0 +1,38 @@ +from flask import request +from kubeflow.kubeflow.crud_backend import ( + api, + decorators, + logging, +) + +from .. import utils +from . import bp + +log = logging.getLogger(__name__) + + +@bp.route("/api/namespaces//tensorboards", methods=["POST"]) +@decorators.request_is_json_type +@decorators.required_body_params("name", "logspath") +def post_tensorboard(namespace): + + body = request.get_json() + log.info("Got body: ", body) + + name = body["name"] + + tensorboard = utils.get_tensorboard_dict(namespace, body) + + log.info("About to create Tensorboard: %s", tensorboard) + api.create_custom_rsrc( + "tensorboard.kubeflow.org", + "v1alpha1", + "tensorboards", + tensorboard, + namespace, + ) + log.info( + "Successfully created Tensorboard %s in namespace %s", name, namespace + ) + + return api.success_response("message", "Tensorboard created successfully.") diff --git a/components/crud-web-apps/tensorboards/backend/app/utils.py b/components/crud-web-apps/tensorboards/backend/app/utils.py new file mode 100644 index 00000000..87d07f54 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/app/utils.py @@ -0,0 +1,31 @@ +from kubeflow.kubeflow.crud_backend import helpers + + +def parse_tensorboard(tensorboard): + """ + Process the Tensorboard object and format it as the UI expects it. + """ + + parsed_tensorboard = { + "name": tensorboard["metadata"]["name"], + "namespace": tensorboard["metadata"]["namespace"], + "logspath": tensorboard["spec"]["logspath"], + "age": helpers.get_age(tensorboard), + } + + return parsed_tensorboard + + +def get_tensorboard_dict(namespace, body): + """ + Create Tensorboard object from request body and format it as a Python dict. + """ + + tensorboard = { + "apiVersion": "tensorboard.kubeflow.org/v1alpha1", + "kind": "Tensorboard", + "metadata": {"name": body["name"], "namespace": namespace, }, + "spec": {"logspath": body["logspath"], }, + } + + return tensorboard diff --git a/components/crud-web-apps/tensorboards/backend/entrypoint.py b/components/crud-web-apps/tensorboards/backend/entrypoint.py new file mode 100644 index 00000000..951ca976 --- /dev/null +++ b/components/crud-web-apps/tensorboards/backend/entrypoint.py @@ -0,0 +1,20 @@ +import os + +from kubeflow.kubeflow.crud_backend import config, logging +import app + +log = logging.getLogger(__name__) + +APP_NAME = os.environ.get("APP_NAME", "Tensorboard Web App") +BACKEND_MODE = os.environ.get("BACKEND_MODE", "prod") # 'prod' or 'dev' + +# Load the Dev config based on BACKEND_MODE env var +if BACKEND_MODE == "dev": + cfg = config.DevConfig +else: + cfg = config.Config + +_app = app.create_app(name=APP_NAME, config_class=cfg) + +if __name__ == "__main__": + _app.run()