From 9b06b0ecad54df34dce3eb22da3577c107202aef Mon Sep 17 00:00:00 2001 From: Ioannis Androulidakis Date: Tue, 6 Nov 2018 20:19:23 +0200 Subject: [PATCH] Manage multiple PVCs via the default JH Spawner UI * Extend default Spawner UI with two new elements: Workspace and Data Volumes * Extend `config.yaml` of the default UI with default values and options for the new Spawner form fields * Add asynchronous logic for handling K8s-native API requests in `spawner.py` (e.g. provision new PVCs, retrieve existing PVCs) * Add 'storageClass` ksonnet parameter to easily configure which PVC provisioner will be used - defaults to 'none' * Remove `disks` and `notebookPVCMount` ksonnet parameters, along with related assignments in `jupyterhub_config.py` * Remove buggy code for attaching multiple PVCs in `jupyterhub_config.py` * Fix flake8 linting errors in `spawner.py` Closes #34 Closes #541 Signed-off-by: Ioannis Androulidakis Signed-off-by: Ilias Tsitsimpis --- kubeflow/core/jupyterhub.libsonnet | 8 +- kubeflow/core/jupyterhub_config.py | 59 +--- kubeflow/core/prototypes/jupyterhub.jsonnet | 3 +- kubeflow/core/tests/jupyterhub_test.jsonnet | 11 +- kubeflow/core/ui/default/config.yaml | 15 + kubeflow/core/ui/default/script.js | 344 ++++++++++++++++++-- kubeflow/core/ui/default/spawner.py | 228 ++++++++++++- kubeflow/core/ui/default/style.css | 28 ++ kubeflow/core/ui/default/template.html | 106 +++++- 9 files changed, 717 insertions(+), 85 deletions(-) diff --git a/kubeflow/core/jupyterhub.libsonnet b/kubeflow/core/jupyterhub.libsonnet index 9bd403dad6d..a02bd1e465d 100644 --- a/kubeflow/core/jupyterhub.libsonnet +++ b/kubeflow/core/jupyterhub.libsonnet @@ -150,10 +150,6 @@ }, ], env: std.prune([ - { - name: "NOTEBOOK_PVC_MOUNT", - value: params.notebookPVCMount, - }, { name: "KF_AUTHENTICATOR", value: params.jupyterHubAuthenticator, @@ -163,8 +159,8 @@ value: params.useJupyterLabAsDefault, }, { - name: "KF_PVC_LIST", - value: params.disks, + name: "STORAGE_CLASS", + value: params.storageClass, }, if params.platform == "gke" then { diff --git a/kubeflow/core/jupyterhub_config.py b/kubeflow/core/jupyterhub_config.py index 7ffca8e58ee..e79de982c8b 100644 --- a/kubeflow/core/jupyterhub_config.py +++ b/kubeflow/core/jupyterhub_config.py @@ -35,8 +35,6 @@ c.KubeSpawner.singleuser_uid = 1000 c.KubeSpawner.singleuser_fs_gid = 100 c.KubeSpawner.singleuser_working_dir = '/home/jovyan' -volumes = [] -volume_mounts = [] # Allow environment vars to override uid and gid. # This allows local host path mounts to be read/writable @@ -65,34 +63,23 @@ def modify_pod_hook(spawner, pod): ################################################### # Persistent volume options ################################################### -# Using persistent storage requires a default storage class. -# TODO(jlewi): Verify this works on minikube. -# see https://github.com/kubeflow/kubeflow/pull/22#issuecomment-350500944 -pvc_mount = os.environ.get('NOTEBOOK_PVC_MOUNT') -if pvc_mount and pvc_mount != 'null': - c.KubeSpawner.user_storage_pvc_ensure = True - c.KubeSpawner.storage_pvc_ensure = True - # How much disk space do we want? - c.KubeSpawner.user_storage_capacity = '10Gi' - c.KubeSpawner.storage_capacity = '10Gi' - c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}' - volumes.append( - { - 'name': 'volume-{username}{servername}', - 'persistentVolumeClaim': { - 'claimName': 'claim-{username}{servername}' - } - } - ) - volume_mounts.append( - { - 'mountPath': pvc_mount, - 'name': 'volume-{username}{servername}' - } - ) +# Set user_storage_pvc_ensure to False to prevent KubeSpawner from handling PVCs +# We natively handle PVCs via KubeFormSpawner and its dedicated methods + +# NOTE: user_storage_pvc_ensure has been deprecated in a future release +c.KubeSpawner.storage_pvc_ensure = False +c.KubeSpawner.user_storage_pvc_ensure = False + +volumes = [] +volume_mounts = [] c.KubeSpawner.volumes = volumes c.KubeSpawner.volume_mounts = volume_mounts + +storage_class = None +if os.environ.get('STORAGE_CLASS') != 'null': + storage_class = os.environ.get('STORAGE_CLASS') + # Set both service_account and singleuser_service_account because # singleuser_service_account has been deprecated in a future release c.KubeSpawner.service_account = 'jupyter-notebook' @@ -107,21 +94,6 @@ def modify_pod_hook(spawner, pod): if os.environ.get('DEFAULT_JUPYTERLAB').lower() == 'true': c.KubeSpawner.default_url = '/lab' -# PVCs -pvcs = os.environ.get('KF_PVC_LIST') -if pvcs and pvcs != 'null': - for pvc in pvcs.split(','): - volumes.append({ - 'name': pvc, - 'persistentVolumeClaim': { - 'claimName': pvc - } - }) - volume_mounts.append({ - 'name': pvc, - 'mountPath': '/mnt/' + pvc - }) - gcp_secret_name = os.environ.get('GCP_SECRET_NAME') if gcp_secret_name: volumes.append({ @@ -137,5 +109,6 @@ def modify_pod_hook(spawner, pod): # Set extra spawner configuration variables c.KubeSpawner.extra_spawner_config = { - 'gcp_secret_name': gcp_secret_name + 'gcp_secret_name': gcp_secret_name, + 'storage_class': storage_class } diff --git a/kubeflow/core/prototypes/jupyterhub.jsonnet b/kubeflow/core/prototypes/jupyterhub.jsonnet index 1a94449f2ac..320746889a8 100644 --- a/kubeflow/core/prototypes/jupyterhub.jsonnet +++ b/kubeflow/core/prototypes/jupyterhub.jsonnet @@ -8,13 +8,12 @@ // @optionalParam image string gcr.io/kubeflow/jupyterhub-k8s:v20180531-3bb991b1 The image to use for JupyterHub. // @optionalParam jupyterHubAuthenticator string null The authenticator to use // @optionalParam useJupyterLabAsDefault string false Set JupterLab interface as the default -// @optionalParam notebookPVCMount string /home/jovyan Mount path for PVC. Set empty to disable PVC -// @optionalParam disks string null Comma separated list of Google persistent disks to attach to jupyter environments. // @optionalParam gcpSecretName string user-gcp-sa The name of the secret containing service account credentials for GCP // @optionalParam notebookUid string -1 UserId of the host user for minikube local fs mount // @optionalParam notebookGid string -1 GroupID of the host user for minikube local fs mount // @optionalParam accessLocalFs string false Set true if mounting a local fs directory that needs to be accessed by Jupyter Notebook in Minikube. // @optionalParam ui string default The JupyterHub Spawner User Interface +// @optionalParam storageClass string null The storageClass to use for PVC management local jupyterhub = import "kubeflow/core/jupyterhub.libsonnet"; local instance = jupyterhub.new(env, params); diff --git a/kubeflow/core/tests/jupyterhub_test.jsonnet b/kubeflow/core/tests/jupyterhub_test.jsonnet index d9d056b082d..77e0f0a17f6 100644 --- a/kubeflow/core/tests/jupyterhub_test.jsonnet +++ b/kubeflow/core/tests/jupyterhub_test.jsonnet @@ -4,16 +4,15 @@ local params = { name: "jupyterhub", platform: "gke", serviceType: "ClusterIP", - disks: "null", gcpSecretName: "user-gcp-sa", image: "gcr.io/kubeflow/jupyterhub-k8s:v20180531-3bb991b1", jupyterHubAuthenticator: "iap", useJupyterLabAsDefault: true, - notebookPVCMount: "/home/jovyan", notebookUid: "-1", notebookGid: "-1", accessLocalFs: "false", ui: "default", + storageClass: "null", }; local env = { namespace: "foo", @@ -98,10 +97,6 @@ std.assertEqual( "/etc/config/jupyterhub_config.py", ], env: [ - { - name: "NOTEBOOK_PVC_MOUNT", - value: "/home/jovyan", - }, { name: "KF_AUTHENTICATOR", value: "iap", @@ -111,7 +106,7 @@ std.assertEqual( value: true, }, { - name: "KF_PVC_LIST", + name: "STORAGE_CLASS", value: "null", }, { @@ -172,6 +167,7 @@ std.assertEqual( resources: [ "pods", "persistentvolumeclaims", + "secrets", ], verbs: [ "get", @@ -179,6 +175,7 @@ std.assertEqual( "list", "create", "delete", + "patch", ], }, { diff --git a/kubeflow/core/ui/default/config.yaml b/kubeflow/core/ui/default/config.yaml index 20b716c035b..facf2e4bd6b 100644 --- a/kubeflow/core/ui/default/config.yaml +++ b/kubeflow/core/ui/default/config.yaml @@ -21,5 +21,20 @@ spawnerFormDefaults: value: '1.0' memory: value: 1.0Gi + workspaceVolume: + value: + type: + value: New + name: + value: workspace + size: + value: '10' + mountPath: + readOnly: true + value: /home/jovyan + accessModes: + value: ReadWriteOnce + dataVolumes: + value: [] extraResources: value: '' diff --git a/kubeflow/core/ui/default/script.js b/kubeflow/core/ui/default/script.js index 9043580e183..114535b4763 100644 --- a/kubeflow/core/ui/default/script.js +++ b/kubeflow/core/ui/default/script.js @@ -22,52 +22,354 @@ $(function() { .val('Spawning...'); }); + // Dynamically change Workspace form fields behavior + setWorkspaceEventListeners(); + // Fill the form with values defined in the YAML config file setDefaultFormValues(); - // Set tooltip to disabled form fields - setTooltipsOnDisabled(); + // Set tooltip to readOnly form fields + setTooltipsOnReadOnly(); }); // Set default values to form fields function setDefaultFormValues() { // Set default Container Image - Disable if specified - formDefaults.image.options.forEach(function(item) { - $('#image').append($('