You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@submarine.apache.org by li...@apache.org on 2021/01/27 04:44:35 UTC

[submarine] branch master updated: SUBMARINE-685. Retain the data in notebook pod even if notebook server is destroyed

This is an automated email from the ASF dual-hosted git repository.

liuxun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new 9a2110a  SUBMARINE-685. Retain the data in notebook pod even if notebook server is destroyed
9a2110a is described below

commit 9a2110aaf70b010454413f67744903d54a966ffc
Author: Charles <ch...@gmail.com>
AuthorDate: Thu Jan 14 15:27:42 2021 +0800

    SUBMARINE-685. Retain the data in notebook pod even if notebook server is destroyed
    
    ### What is this PR for?
    Add 10GB persistent volume for each notebook which mounting path is /home/jovyan/workspace.
     I use local persistent volume which mounting path of node is /mnt since Submarine do not use any storage class yet.
    
    ### What type of PR is it?
    [Improvement]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    [SUBMARINE-685](https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-685?filter=allopenissues)
    ### How should this be tested?
    [Travis-CI](https://travis-ci.com/github/charlie0220/submarine/builds/210378125)
    
    ### Screenshots (if appropriate)
    ![pv-test](https://user-images.githubusercontent.com/50860453/103065990-e4535200-45f2-11eb-950e-e1290427dc25.gif)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Author: Charles <ch...@gmail.com>
    
    Signed-off-by: Liu Xun <li...@apache.org>
    
    Closes #481 from charlie0220/SUBMARINE-685 and squashes the following commits:
    
    c4821a5 [Charles] add comments for Thread.sleep and some variables name
    0884d43 [Charles] add Thread.sleep in NotebookRestApiIT.java waiting for deleting pvc and pv
    1f842bf [Charles] SUBMARINE-685. Retain the data in notebook pod even if notebook server is destroyed.
---
 .../server/submitter/k8s/K8sSubmitter.java         | 59 ++++++++++++++--------
 .../submitter/k8s/parser/NotebookSpecParser.java   | 20 ++++++++
 .../server/submitter/k8s/util/NotebookUtils.java   |  4 ++
 .../apache/submarine/rest/NotebookRestApiIT.java   |  5 ++
 4 files changed, 68 insertions(+), 20 deletions(-)

diff --git a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
index 5521f99..6d5b50d 100644
--- a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
+++ b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
@@ -43,6 +43,7 @@ import io.kubernetes.client.models.V1PersistentVolumeClaim;
 import io.kubernetes.client.models.V1Pod;
 import io.kubernetes.client.models.V1PodList;
 import io.kubernetes.client.models.V1Service;
+import io.kubernetes.client.models.V1PersistentVolumeClaimVolumeSource;
 import io.kubernetes.client.models.V1Status;
 import io.kubernetes.client.util.ClientBuilder;
 import io.kubernetes.client.util.KubeConfig;
@@ -129,12 +130,17 @@ public class K8sSubmitter implements Submitter {
   public Experiment createExperiment(ExperimentSpec spec) throws SubmarineRuntimeException {
     Experiment experiment;
     final String name = spec.getMeta().getName();
+    final String pvName = TensorboardUtils.PV_PREFIX + name;
+    final String hostPath = TensorboardUtils.HOST_PREFIX + name;
+    final String storage = TensorboardUtils.STORAGE;
+    final String pvcName = TensorboardUtils.PVC_PREFIX + name;
+    final String volume = TensorboardUtils.PV_PREFIX + name;
 
     try {
       MLJob mlJob = ExperimentSpecParser.parseJob(spec);
 
-      createTFBoardPersistentVolume(name);
-      createTFBoardPersistentVolumeClaim(name, spec.getMeta().getNamespace());
+      createPersistentVolume(pvName, hostPath, storage);
+      createPersistentVolumeClaim(pvcName, spec.getMeta().getNamespace(), volume, storage);
       createTFBoard(name, spec.getMeta().getNamespace());
 
       Object object = api.createNamespacedCustomObject(mlJob.getGroup(), mlJob.getVersion(),
@@ -188,12 +194,14 @@ public class K8sSubmitter implements Submitter {
   public Experiment deleteExperiment(ExperimentSpec spec) throws SubmarineRuntimeException {
     Experiment experiment;
     final String name = spec.getMeta().getName(); // spec.getMeta().getEnvVars().get(RestConstants.JOB_ID);
+    final String pvName = TensorboardUtils.PV_PREFIX + name;
+    final String pvcName = TensorboardUtils.PVC_PREFIX + name;
 
     try {
       MLJob mlJob = ExperimentSpecParser.parseJob(spec);
 
-      deleteTFBoardPersistentVolume(name);
-      deleteTFBoardPersistentVolumeClaim(name, spec.getMeta().getNamespace());
+      deletePersistentVolume(pvName);
+      deletePersistentVolumeClaim(pvcName, spec.getMeta().getNamespace());
       deleteTFBoard(name, spec.getMeta().getNamespace());
 
       Object object = api.deleteNamespacedCustomObject(mlJob.getGroup(), mlJob.getVersion(),
@@ -277,12 +285,29 @@ public class K8sSubmitter implements Submitter {
   @Override
   public Notebook createNotebook(NotebookSpec spec) throws SubmarineRuntimeException {
     Notebook notebook;
+    final String name = spec.getMeta().getName();
+    final String pvName = NotebookUtils.PV_PREFIX + name;
+    final String host = NotebookUtils.HOST_PATH;
+    final String storage = NotebookUtils.STORAGE;
+    final String pvcName = NotebookUtils.PVC_PREFIX + name;
     try {
       // create notebook custom resource
       NotebookCR notebookCR = NotebookSpecParser.parseNotebook(spec);
       Map<String, String> labels = new HashMap<>();
       labels.put(NotebookCR.NOTEBOOK_OWNER_SELECTOR_KET, spec.getMeta().getOwnerId());
       notebookCR.getMetadata().setLabels(labels);
+
+      // create persistent volume
+      createPersistentVolume(pvName, host, storage);
+
+      // create persistent volume claim
+      createPersistentVolumeClaim(pvcName, spec.getMeta().getNamespace(), pvName, storage);
+
+      // bind persistent volume claim
+      V1PersistentVolumeClaimVolumeSource pvcSource = new V1PersistentVolumeClaimVolumeSource()
+              .claimName(pvcName);
+      notebookCR.getSpec().getTemplate().getSpec().getVolumes().get(0).persistentVolumeClaim(pvcSource);
+
       Object object = api.createNamespacedCustomObject(notebookCR.getGroup(), notebookCR.getVersion(),
               notebookCR.getMetadata().getNamespace(), notebookCR.getPlural(), notebookCR, "true");
       notebook = NotebookUtils.parseObject(object, NotebookUtils.ParseOpt.PARSE_OPT_CREATE);
@@ -319,6 +344,9 @@ public class K8sSubmitter implements Submitter {
   @Override
   public Notebook deleteNotebook(NotebookSpec spec) throws SubmarineRuntimeException {
     Notebook notebook;
+    final String name = spec.getMeta().getName();
+    final String pvName = NotebookUtils.PV_PREFIX + name;
+    final String pvcName = NotebookUtils.PVC_PREFIX + name;
     try {
       NotebookCR notebookCR = NotebookSpecParser.parseNotebook(spec);
       Object object = api.deleteNamespacedCustomObject(notebookCR.getGroup(), notebookCR.getVersion(),
@@ -328,6 +356,8 @@ public class K8sSubmitter implements Submitter {
               null, null, null);
       notebook = NotebookUtils.parseObject(object, NotebookUtils.ParseOpt.PARSE_OPT_DELETE);
       deleteIngressRoute(notebookCR.getMetadata().getNamespace(), notebookCR.getMetadata().getName());
+      deletePersistentVolumeClaim(pvcName, spec.getMeta().getNamespace());
+      deletePersistentVolume(pvName);
     } catch (ApiException e) {
       throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
     }
@@ -411,11 +441,7 @@ public class K8sSubmitter implements Submitter {
     }
   }
 
-  public void createTFBoardPersistentVolume(String name) throws ApiException {
-    final String pvName = TensorboardUtils.PV_PREFIX + name;
-    final String hostPath = TensorboardUtils.HOST_PREFIX + name;
-    final String storage = TensorboardUtils.STORAGE;
-
+  public void createPersistentVolume(String pvName, String hostPath, String storage) throws ApiException {
     V1PersistentVolume pv = VolumeSpecParser.parsePersistentVolume(pvName, hostPath, storage);
 
     try {
@@ -426,14 +452,12 @@ public class K8sSubmitter implements Submitter {
     }
   }
 
-  public void deleteTFBoardPersistentVolume(String name) throws ApiException {
+  public void deletePersistentVolume(String pvName) throws ApiException {
     /*
     This version of Kubernetes-client/java has bug here.
     It will trigger exception as in https://github.com/kubernetes-client/java/issues/86
     but it can still work fine and delete the PV.
     */
-    final String pvName = TensorboardUtils.PV_PREFIX + name;
-
     try {
       V1Status result = coreApi.deletePersistentVolume(
               pvName, "true", null,
@@ -454,11 +478,8 @@ public class K8sSubmitter implements Submitter {
     }
   }
 
-  public void createTFBoardPersistentVolumeClaim(String name, String namespace) throws ApiException {
-    final String pvcName = TensorboardUtils.PVC_PREFIX + name;
-    final String volume = TensorboardUtils.PV_PREFIX + name;
-    final String storage = TensorboardUtils.STORAGE;
-
+  public void createPersistentVolumeClaim(String pvcName, String namespace, String volume, String storage)
+          throws ApiException {
     V1PersistentVolumeClaim pvc = VolumeSpecParser.parsePersistentVolumeClaim(pvcName, volume, storage);
 
     try {
@@ -471,14 +492,12 @@ public class K8sSubmitter implements Submitter {
     }
   }
 
-  public void deleteTFBoardPersistentVolumeClaim(String name, String namespace) throws ApiException {
+  public void deletePersistentVolumeClaim(String pvcName, String namespace) throws ApiException {
     /*
     This version of Kubernetes-client/java has bug here.
     It will trigger exception as in https://github.com/kubernetes-client/java/issues/86
     but it can still work fine and delete the PVC
     */
-    final String pvcName = TensorboardUtils.PVC_PREFIX + name;
-
     try {
       V1Status result = coreApi.deleteNamespacedPersistentVolumeClaim(
                 pvcName, namespace, "true",
diff --git a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/parser/NotebookSpecParser.java b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/parser/NotebookSpecParser.java
index e0889b4..bdb4b8b 100644
--- a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/parser/NotebookSpecParser.java
+++ b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/parser/NotebookSpecParser.java
@@ -26,6 +26,8 @@ import io.kubernetes.client.models.V1ObjectMeta;
 import io.kubernetes.client.models.V1PodTemplateSpec;
 import io.kubernetes.client.models.V1PodSpec;
 import io.kubernetes.client.models.V1ResourceRequirements;
+import io.kubernetes.client.models.V1Volume;
+import io.kubernetes.client.models.V1VolumeMount;
 
 import org.apache.submarine.commons.utils.SubmarineConfVars;
 import org.apache.submarine.commons.utils.SubmarineConfiguration;
@@ -132,9 +134,27 @@ public class NotebookSpecParser {
       container.setResources(resources);
     }
 
+    // Volume spec
+    final String DEFAULT_MOUNT_PATH = "/home/jovyan/workspace";
+
+    List<V1VolumeMount> volumeMountList = new ArrayList<>();
+    V1VolumeMount  volumeMount = new V1VolumeMount();
+    volumeMount.setMountPath(DEFAULT_MOUNT_PATH);
+    volumeMount.setName("notebook-pv-" + notebookSpec.getMeta().getName());
+    volumeMountList.add(volumeMount);
+    container.setVolumeMounts(volumeMountList);
+
     containers.add(container);
     podSpec.setContainers(containers);
 
+    // create volume object for persistent volume
+    List<V1Volume> volumeList = new ArrayList<>();
+    V1Volume volume = new V1Volume();
+    String volumeName = "notebook-pv-" + notebookSpec.getMeta().getName();
+    volume.setName(volumeName);
+    volumeList.add(volume);
+    podSpec.setVolumes(volumeList);
+
     podTemplateSpec.setSpec(podSpec);
     return podTemplateSpec;
   }
diff --git a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/NotebookUtils.java b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/NotebookUtils.java
index cf33ff7..33f83fe 100644
--- a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/NotebookUtils.java
+++ b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/NotebookUtils.java
@@ -43,6 +43,10 @@ import org.slf4j.LoggerFactory;
 public class NotebookUtils {
 
   private static final Logger LOG = LoggerFactory.getLogger(NotebookUtils.class);
+  public static final String STORAGE = "10Gi";
+  public static final String PV_PREFIX = "notebook-pv-";
+  public static final String PVC_PREFIX = "notebook-pvc-";
+  public static final String HOST_PATH = "/mnt";
 
   public enum ParseOpt {
     PARSE_OPT_CREATE,
diff --git a/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
index d47adcf..e956477 100644
--- a/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
+++ b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
@@ -56,6 +56,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.io.FileReader;
 import java.io.IOException;
+import java.lang.Thread;
 import java.util.Date;
 
 @SuppressWarnings("rawtypes")
@@ -187,6 +188,8 @@ public class NotebookRestApiIT extends AbstractSubmarineServerTest {
             gson.fromJson(gson.toJson(jsonResponse.getResult()), Environment.class);
     Assert.assertEquals(ENV_NAME, getEnvironment.getEnvironmentSpec().getName());
 
+    // waiting for deleting previous persistent volume
+    Thread.sleep(15000);
     // create notebook instances
     LOG.info("Create notebook servers by Notebook REST API");
     String body = loadContent("notebook/notebook-req.json");
@@ -223,6 +226,8 @@ public class NotebookRestApiIT extends AbstractSubmarineServerTest {
   }
 
   private void runTest(String body, String contentType) throws Exception {
+    // waiting for deleting previous persistent volume
+    Thread.sleep(15000);
     // create
     LOG.info("Create a notebook server by Notebook REST API");
     PostMethod postMethod = httpPost(BASE_API_PATH, body, contentType);


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@submarine.apache.org
For additional commands, e-mail: dev-help@submarine.apache.org