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 2020/09/12 07:51:53 UTC

[submarine] branch master updated: SUBMARINE-558. Define Swagger API for pre-defined template submission

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 57eb259  SUBMARINE-558. Define Swagger API for pre-defined template submission
57eb259 is described below

commit 57eb259f6aebc2e1c918eff7ae13d027f0ef891d
Author: JohnTing <jo...@gmail.com>
AuthorDate: Wed Sep 9 02:45:38 2020 +0800

    SUBMARINE-558. Define Swagger API for pre-defined template submission
    
    ### What is this PR for?
    Make basic rest api for submi template.
    
    Convert submitted template to experiment
    post
    http://localhost/V1/template/submit
    
    ### What type of PR is it?
    [Bug Fix | Improvement | Feature | Documentation | Hot Fix | Refactoring]
    
    ### Todos
    * [x] - API
    * [x] - test
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-558
    
    ### How should this be tested?
    
    ```sh
    # Register experiment template
    curl -X POST -H "Content-Type: application/json" -d '
    {
      "name": "tf-mnist-test",
      "author": "author",
      "description": "This is a template to run tf-mnist\n",
      "parameters": [
        {
          "name": "training.learning_rate",
          "value": 0.1,
          "required": true,
          "description": " mnist learning_rate "
        },
        {
          "name": "training.batch_size",
          "value": 150,
          "required": false,
          "description": "This is batch size of training"
        }
      ],
      "experimentSpec": {
        "meta": {
          "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate={{training.learning_rate}} --batch_size={{training.batch_size}}",
          "name": "tf-mnist-template-test",
          "envVars": {
            "ENV1": "ENV1"
          },
          "framework": "TensorFlow",
          "namespace": "default"
        },
        "spec": {
          "Ps": {
            "replicas": 1,
            "resources": "cpu=1,memory=1024M"
          },
          "Worker": {
            "replicas": 1,
            "resources": "cpu=1,memory=1024M"
          }
        },
        "environment": {
          "image": "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0"
        }
      }
    }
    ' http://127.0.0.1:8080/api/v1/template
    ```
    
    ```sh
    # Submit experiment template to experiment
    curl -X POST -H "Content-Type: application/json" -d '
    {
        "name": "tf-mnist-test",
        "params": {
            "training.learning_rate":"0.01",
            "training.batch_size":"150"
        }
    }
    ' http://127.0.0.1:8080/api/v1/template/submit
    ```
    
    If the submission is successful, it will return
    ```json
    {
        "status": "OK",
        "code": 200,
        "success": true,
        "message": null,
        "result": {
            "experimentId": "experiment_1597853926000_0035",
            "name": "tf-mnist-template-test",
            "uid": "0a79d641-871e-4ba6-9e3e-eab1d4690f4e",
            "status": "Accepted",
            "acceptedTime": "2020-08-22T18:36:32.000+08:00",
            "createdTime": null,
            "runningTime": null,
            "finishedTime": null,
            "spec": {
                "meta": {
                    "name": "tf-mnist-template-test",
                    "namespace": "default",
                    "framework": "TensorFlow",
                    "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate=0.01 --batch_size=150",
                    "envVars": {
                        "ENV1": "ENV1"
                    }
                },
                "environment": {
                    "name": null,
                    "dockerImage": null,
                    "kernelSpec": null,
                    "description": null,
                    "image": "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0"
                },
                "spec": {
                    "Ps": {
                        "replicas": 1,
                        "resources": "cpu=1,memory=1024M",
                        "name": null,
                        "image": null,
                        "cmd": null,
                        "envVars": null,
                        "resourceMap": {
                            "memory": "1024M",
                            "cpu": "1"
                        }
                    },
                    "Worker": {
                        "replicas": 1,
                        "resources": "cpu=1,memory=1024M",
                        "name": null,
                        "image": null,
                        "cmd": null,
                        "envVars": null,
                        "resourceMap": {
                            "memory": "1024M",
                            "cpu": "1"
                        }
                    }
                }
            }
        },
        "attributes": {}
    }
    ```
    
    ### Screenshots (if appropriate)
    ![image](https://user-images.githubusercontent.com/19265751/90865269-b64fce00-e3c4-11ea-8e9e-6f3a3a24c892.png)
    
    ### Questions:
    * Does the licenses files need update? Yes/No
    * Is there breaking changes for older versions? Yes/No
    * Does this needs documentation? Yes/No
    
    Author: JohnTing <jo...@gmail.com>
    
    Closes #382 from JohnTing/SUBMARINE-558 and squashes the following commits:
    
    1ea2588 [JohnTing] change
    e98083b [JohnTing] change
    b7b6dd4 [JohnTing] ExperimentAndTemplateMixed api
    eca2ec3 [JohnTing] change3
    b358d3b [JohnTing] change3
    6a4bad2 [JohnTing] change2
    7fc82ef [JohnTing] change
    27a86f3 [JohnTing] Update submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java
    1de1d4f [JohnTing] Update submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
    386ff82 [JohnTing] Update submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
    a28d1a5 [JohnTing] Update submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
    e11a097 [JohnTing] add simple doc
    64cc6a7 [JohnTing] test1
    5060482 [JohnTing] Add testList, testUpdate
    4b264cb [JohnTing] usable
---
 docs/userdocs/k8s/use-experiment-template.md       | 142 +++++++++++++++++++++
 .../ExperimentTemplateSubmit.java                  |  44 +++++++
 .../server/api/spec/ExperimentTemplateSpec.java    |   8 ++
 .../ExperimentTemplateManager.java                 |  87 ++++++++++---
 .../submarine/server/rest/ExperimentRestApi.java   |  33 +++++
 .../submarine/server/rest/RestConstants.java       |   2 +
 .../server/rest/ExperimentTemplateRestApiTest.java |  14 +-
 .../test_template_1.json                           |  18 +--
 .../experimenttemplate/test_template_2.json        |  43 -------
 .../rest/ExperimentTemplateManagerRestApiIT.java   | 119 ++++++++++++++---
 10 files changed, 415 insertions(+), 95 deletions(-)

diff --git a/docs/userdocs/k8s/use-experiment-template.md b/docs/userdocs/k8s/use-experiment-template.md
new file mode 100644
index 0000000..732b29a
--- /dev/null
+++ b/docs/userdocs/k8s/use-experiment-template.md
@@ -0,0 +1,142 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+# Use Experiment Template Guide
+
+The {{name}} variable in "experimentSpec" will be replace by the parameters value.
+
+JSON Format example:
+```json
+{
+  "name": "tf-mnist-test",
+  "author": "author",
+  "description": "This is a template to run tf-mnist",
+  "parameters": [
+    {
+      "name": "training.learning_rate",
+      "value": 0.1,
+      "required": true,
+      "description": " mnist learning_rate "
+    },
+    {
+      "name": "training.batch_size",
+      "value": 150,
+      "required": false,
+      "description": "This is batch size of training"
+    }
+  ],
+  "experimentSpec": {
+    "meta": {
+      "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate={{training.learning_rate}} --batch_size={{training.batch_size}}",
+      "name": "tf-mnist-template-test",
+      "envVars": {
+        "ENV1": "ENV1"
+      },
+      "framework": "TensorFlow",
+      "namespace": "default"
+    },
+    "spec": {
+      "Ps": {
+        "replicas": 1,
+        "resources": "cpu=1,memory=1024M"
+      },
+      "Worker": {
+        "replicas": 1,
+        "resources": "cpu=1,memory=1024M"
+      }
+    },
+    "environment": {
+      "image": "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0"
+    }
+  }
+}
+```
+
+### Register experiment template
+```sh
+curl -X POST -H "Content-Type: application/json" -d '
+{
+  "name": "tf-mnist-test",
+  "author": "author",
+  "description": "This is a template to run tf-mnist",
+  "parameters": [
+    {
+      "name": "training.learning_rate",
+      "value": 0.1,
+      "required": true,
+      "description": " mnist learning_rate "
+    },
+    {
+      "name": "training.batch_size",
+      "value": 150,
+      "required": false,
+      "description": "This is batch size of training"
+    }
+  ],
+  "experimentSpec": {
+    "meta": {
+      "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate={{training.learning_rate}} --batch_size={{training.batch_size}}",
+      "name": "tf-mnist-template-test",
+      "envVars": {
+        "ENV1": "ENV1"
+      },
+      "framework": "TensorFlow",
+      "namespace": "default"
+    },
+    "spec": {
+      "Ps": {
+        "replicas": 1,
+        "resources": "cpu=1,memory=1024M"
+      },
+      "Worker": {
+        "replicas": 1,
+        "resources": "cpu=1,memory=1024M"
+      }
+    },
+    "environment": {
+      "image": "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0"
+    }
+  }
+}
+' http://127.0.0.1:8080/api/v1/template
+```
+
+JSON Format example:
+```json
+{
+    "name": "tf-mnist-test", 
+    "params": {
+        "training.learning_rate":"0.01", 
+        "training.batch_size":"150"
+    }
+}
+```
+
+### Submit experiment template
+```sh
+curl -X POST -H "Content-Type: application/json" -d '
+{
+    "name": "tf-mnist-test", 
+    "params": {
+        "training.learning_rate":"0.01", 
+        "training.batch_size":"150"
+    }
+}
+' http://127.0.0.1:8080/api/v1/template/submit
+```
diff --git a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateSubmit.java b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateSubmit.java
new file mode 100644
index 0000000..f5a327f
--- /dev/null
+++ b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateSubmit.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.submarine.server.api.experimenttemplate;
+
+import java.util.Map;
+
+public class ExperimentTemplateSubmit {
+  // template name
+  String name;
+  Map<String, String> params;
+
+  public String getName() {
+    return this.name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public Map<String, String> getParams() {
+    return params;
+  }
+
+  public void setParams(Map<String, String> params) {
+    this.params = params;
+  }
+}
diff --git a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateSpec.java b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateSpec.java
index 8dc5b58..1b0deaa 100644
--- a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateSpec.java
+++ b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateSpec.java
@@ -67,4 +67,12 @@ public class ExperimentTemplateSpec {
   public void setParameters(List<ExperimentTemplateParamSpec> parameters) {
     this.parameters = parameters;
   }
+
+  public List<ExperimentTemplateParamSpec> getExperimentTemplateParamSpec() {
+    return this.parameters;
+  }
+
+  public void setExperimentTemplateParamSpec(List<ExperimentTemplateParamSpec> parameters) {
+    this.parameters = parameters;
+  }
 }
diff --git a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
index feac673..cd8c828 100644
--- a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
+++ b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
@@ -34,13 +34,17 @@ import javax.ws.rs.core.Response.Status;
 import org.apache.ibatis.session.SqlSession;
 import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
 import org.apache.submarine.server.SubmarineServer;
+import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplate;
 import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplateId;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplateSubmit;
 import org.apache.submarine.server.api.spec.ExperimentTemplateParamSpec;
 import org.apache.submarine.server.api.spec.ExperimentTemplateSpec;
 import org.apache.submarine.server.database.utils.MyBatisUtil;
+import org.apache.submarine.server.experiment.ExperimentManager;
 import org.apache.submarine.server.experimenttemplate.database.entity.ExperimentTemplateEntity;
 import org.apache.submarine.server.experimenttemplate.database.mappers.ExperimentTemplateMapper;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -137,19 +141,27 @@ public class ExperimentTemplateManager {
       }
       sqlSession.commit();
       
-      ExperimentTemplate experimentTemplate = new ExperimentTemplate();
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+          "Unable to insert or update the experimentTemplate spec: " + e.getMessage());
+    }
+
+    ExperimentTemplate experimentTemplate;
+    try {
+      experimentTemplate = new ExperimentTemplate();
       experimentTemplate.setExperimentTemplateId(ExperimentTemplateId.fromString(experimentTemplateId));
       experimentTemplate.setExperimentTemplateSpec(spec);
       
-      // Update cache
-      cachedExperimentTemplates.putIfAbsent(spec.getName(), experimentTemplate);
-
-      return experimentTemplate;
     } catch (Exception e) {
       LOG.error(e.getMessage(), e);
       throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
-          "Unable to process the experimentTemplate spec.");
+          "Unable to parse the experimentTemplate spec: " + e.getMessage());
     }
+    // Update cache
+    cachedExperimentTemplates.putIfAbsent(spec.getName(), experimentTemplate);
+
+    return experimentTemplate;
   }
 
   private ExperimentTemplateId generateExperimentTemplateId() {
@@ -276,19 +288,59 @@ public class ExperimentTemplateManager {
     return tpl;
   }
 
-  private ExperimentTemplateSpec parameterMapping(String spec) {
 
+  /**
+   * Create ExperimentTemplate
+   * 
+   * @param SubmittedParam experimentTemplate spec
+   * @return Experiment experiment
+   * @throws SubmarineRuntimeException the service error
+   */
+  public Experiment submitExperimentTemplate(ExperimentTemplateSubmit SubmittedParam) 
+        throws SubmarineRuntimeException {
+
+    if (SubmittedParam == null) {
+      throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(), 
+            "Invalid ExperimentTemplateSubmit spec.");
+    }
+
+    ExperimentTemplate experimentTemplate = getExperimentTemplate(SubmittedParam.getName());
+    Map<String, String> params = SubmittedParam.getParams();
+
+
+    for (ExperimentTemplateParamSpec paramSpec: 
+          experimentTemplate.getExperimentTemplateSpec().getExperimentTemplateParamSpec()) {
+
+      String value = params.get(paramSpec.getName());
+      if (value != null) {
+        paramSpec.setValue(value);
+      }
+    }
+    String spec = new Gson().toJson(experimentTemplate.getExperimentTemplateSpec());
+
+    ExperimentTemplateSpec experimentTemplateSpec = parameterMapping(spec, params);
+        
+    return ExperimentManager.getInstance().createExperiment(experimentTemplateSpec.getExperimentSpec());
+  }
+
+  private ExperimentTemplateSpec parameterMapping(String spec) {
     ExperimentTemplateSpec tplSpec = new Gson().fromJson(spec, ExperimentTemplateSpec.class);
 
-    Map<String, String> parmMap = new HashMap<String, String>();
-    for (ExperimentTemplateParamSpec parm : tplSpec.getParameters()) {
+    Map<String, String> paramMap = new HashMap<String, String>();
+    for (ExperimentTemplateParamSpec parm : tplSpec.getExperimentTemplateParamSpec()) {
       if (parm.getValue() != null) {
-        parmMap.put(parm.getName(), parm.getValue());
+        paramMap.put(parm.getName(), parm.getValue());
       } else {
-        parmMap.put(parm.getName(), "");
+        paramMap.put(parm.getName(), "");
       }
     }
 
+    return parameterMapping(spec, paramMap);
+  }
+
+  // Use params to replace the content of spec
+  private ExperimentTemplateSpec parameterMapping(String spec, Map<String, String> paramMap) {
+
     Pattern pattern = Pattern.compile("\\{\\{(.+?)\\}\\}");
     StringBuffer sb = new StringBuffer();
     Matcher matcher = pattern.matcher(spec);
@@ -297,33 +349,34 @@ public class ExperimentTemplateManager {
 
     while (matcher.find()) {
       final String key = matcher.group(1);
-      final String replacement = parmMap.get(key);
+      final String replacement = paramMap.get(key);
       if (replacement == null) {
         unmappedKeys.add(key);
       }
       else {
         matcher.appendReplacement(sb, replacement);
       }
-      parmMap.remove(key);
+      paramMap.remove(key);
     }
     matcher.appendTail(sb);
 
-    if (parmMap.size() > 0) {
+    if (paramMap.size() > 0) {
       throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
-            "Parameters contains unused key: " + parmMap.keySet());
+            "Parameters contains unused key: " + paramMap.keySet());
     }
 
     if (unmappedKeys.size() > 0) {
       throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
           "Template contains unmapped key: " + unmappedKeys);
     }  
+    ExperimentTemplateSpec experimentTemplateSpec;
 
     try {
-      tplSpec = new Gson().fromJson(sb.toString(), ExperimentTemplateSpec.class);
+      experimentTemplateSpec = new Gson().fromJson(sb.toString(), ExperimentTemplateSpec.class);
     } catch (Exception e) {
       throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
           "Template mapping fail: " + e.getMessage() + sb.toString());
     }
-    return tplSpec;
+    return experimentTemplateSpec;
   }
 }
diff --git a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentRestApi.java b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentRestApi.java
index b57ae1d..b0cd30e 100644
--- a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentRestApi.java
+++ b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentRestApi.java
@@ -33,6 +33,7 @@ import javax.ws.rs.core.Response;
 import java.util.List;
 
 import com.google.common.annotations.VisibleForTesting;
+
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -41,7 +42,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
 import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.experiment.ExperimentManager;
+import org.apache.submarine.server.experimenttemplate.ExperimentTemplateManager;
 import org.apache.submarine.server.api.experiment.ExperimentLog;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplateSubmit;
 import org.apache.submarine.server.api.spec.ExperimentSpec;
 import org.apache.submarine.server.response.JsonResponse;
 
@@ -77,12 +80,14 @@ public class ExperimentRestApi {
         .success(true).result("Pong").build();
   }
 
+  
   /**
    * Returns the contents of {@link Experiment} that submitted by user.
    *
    * @param spec spec
    * @return the contents of experiment
    */
+  
   @POST
   @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
   @Operation(summary = "Create an experiment",
@@ -101,6 +106,34 @@ public class ExperimentRestApi {
   }
 
   /**
+   * Returns the contents of {@link Experiment} that submitted by user.
+   *
+   * @param id template id
+   * @param spec 
+   * @return the contents of experiment
+   */
+  @POST
+  @Path("/{name}")
+  @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
+  @Operation(summary = "use experiment template to create an experiment",
+      tags = {"experiment"},
+      responses = {
+          @ApiResponse(description = "successful operation", content = @Content(
+              schema = @Schema(implementation = JsonResponse.class)))})
+  public Response SubmitExperimentTemplate(@PathParam("name") String name, 
+        ExperimentTemplateSubmit spec) {
+    try {
+      spec.setName(name);
+      
+      Experiment experiment = ExperimentTemplateManager.getInstance().submitExperimentTemplate(spec);
+      return new JsonResponse.Builder<Experiment>(Response.Status.OK)
+          .success(true).result(experiment).build();
+    } catch (SubmarineRuntimeException e) {
+      return parseExperimentServiceException(e);
+    }
+  }
+
+  /**
    * List all experiment for the user
    *
    * @return experiment list
diff --git a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/RestConstants.java b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/RestConstants.java
index 699109c..da66402 100644
--- a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/RestConstants.java
+++ b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/RestConstants.java
@@ -61,6 +61,8 @@ public class RestConstants {
   
   public static final String EXPERIMENT_TEMPLATE_ID = "id";
 
+  public static final String EXPERIMENT_TEMPLATE_SUBMIT = "submit";
+
   /**
    * Notebook
    */
diff --git a/submarine-server/server-core/src/test/java/org/apache/submarine/server/rest/ExperimentTemplateRestApiTest.java b/submarine-server/server-core/src/test/java/org/apache/submarine/server/rest/ExperimentTemplateRestApiTest.java
index 1813609..6945de5 100644
--- a/submarine-server/server-core/src/test/java/org/apache/submarine/server/rest/ExperimentTemplateRestApiTest.java
+++ b/submarine-server/server-core/src/test/java/org/apache/submarine/server/rest/ExperimentTemplateRestApiTest.java
@@ -49,6 +49,8 @@ public class ExperimentTemplateRestApiTest {
   private static GsonBuilder gsonBuilder = new GsonBuilder();
   private static Gson gson = gsonBuilder.setDateFormat("yyyy-MM-dd HH:mm:ss").create();
 
+  protected static String TPL_FILE = "experimentTemplate/test_template_1.json";
+
   @BeforeClass
   public static void init() {
     SubmarineConfiguration submarineConf = SubmarineConfiguration.getInstance();
@@ -62,13 +64,13 @@ public class ExperimentTemplateRestApiTest {
 
   @Before
   public void createAndUpdateExperimentTemplate() {
-    String body = loadContent("experimenttemplate/test_template_1.json");
+    String body = loadContent(TPL_FILE);
     experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
     
     // Create ExperimentTemplate
     Response createEnvResponse = experimentTemplateStoreApi.createExperimentTemplate(experimentTemplateSpec);
     assertEquals(Response.Status.OK.getStatusCode(), createEnvResponse.getStatus());
-
+    
     // Update ExperimentTemplate
     experimentTemplateSpec.setDescription("newdescription");
     Response updateTplResponse = experimentTemplateStoreApi.
@@ -78,8 +80,8 @@ public class ExperimentTemplateRestApiTest {
 
   @After
   public void deleteExperimentTemplate() {
-
-    String body = loadContent("experimenttemplate/test_template_1.json");
+    
+    String body = loadContent(TPL_FILE);
     experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
 
     Response deleteEnvResponse = experimentTemplateStoreApi.
@@ -90,7 +92,7 @@ public class ExperimentTemplateRestApiTest {
   @Test
   public void getExperimentTemplate() {
 
-    String body = loadContent("experimenttemplate/test_template_1.json");
+    String body = loadContent(TPL_FILE);
     experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
 
     Response getEnvResponse = experimentTemplateStoreApi.
@@ -111,7 +113,7 @@ public class ExperimentTemplateRestApiTest {
   @Test
   public void listExperimentTemplate() {
 
-    String body = loadContent("experimenttemplate/test_template_1.json");
+    String body = loadContent(TPL_FILE);
     experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
 
     Response getEnvResponse = experimentTemplateStoreApi.listExperimentTemplate("");
diff --git a/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_1.json b/submarine-server/server-core/src/test/resources/experimentTemplate/test_template_1.json
similarity index 60%
rename from submarine-server/server-core/src/test/resources/experimenttemplate/test_template_1.json
rename to submarine-server/server-core/src/test/resources/experimentTemplate/test_template_1.json
index 61448d5..c7e6d3c 100644
--- a/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_1.json
+++ b/submarine-server/server-core/src/test/resources/experimentTemplate/test_template_1.json
@@ -1,12 +1,13 @@
 {
-  "name": "tf-mnist-test",
-  "author": "author",
-  "description": "This is a template to run tf-mnist\n",
+  "name": "tf-mnist-test_1",
+  "author": "test_author_1",
+  "description": "This is a test template to run tf-mnist\n",
   "parameters": [
     {
-      "name": "input.train_data",
+      "name": "training.learning_rate",
+      "value": 0.1,
       "required": true,
-      "description": "train data is expected in SVM format, and can be stored in HDFS/S3 \n"
+      "description": " mnist learning_rate "
     },
     {
       "name": "training.batch_size",
@@ -17,11 +18,10 @@
   ],
   "experimentSpec": {
     "meta": {
-      "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate=0.01 --batch_size={{training.batch_size}}",
-      "name": "tf-mnist-json",
+      "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate={{training.learning_rate}} --batch_size={{training.batch_size}}",
+      "name": "tf-mnist-template-test1",
       "envVars": {
-        "input_path": "{{input.train_data}}", 
-        "ENV2": "ENV2"
+        "ENV1": "ENV1"
       },
       "framework": "TensorFlow",
       "namespace": "default"
diff --git a/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_2.json b/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_2.json
deleted file mode 100644
index 0c8cae1..0000000
--- a/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_2.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-  "name": "tf-mnist-test2",
-  "author": "author",
-  "description": "This is a template to run tf-mnist\n",
-  "parameters": [
-    {
-      "name": "input.train_data",
-      "required": true,
-      "description": "train data is expected in SVM format, and can be stored in HDFS/S3 \n"
-    },
-    {
-      "name": "training.batch_size",
-      "value": 150,
-      "required": false,
-      "description": "This is batch size of training"
-    }
-  ],
-  "experimentSpec": {
-    "meta": {
-      "cmd": "python /var/tf_mnist/mnist_with_summaries.py --log_dir=/train/log --learning_rate=0.01 --batch_size={{training.batch_size}}",
-      "name": "tf-mnist-json",
-      "envVars": {
-        "input_path": "{{input.train_data}}", 
-        "ENV2": "ENV2"
-      },
-      "framework": "TensorFlow",
-      "namespace": "default"
-    },
-    "spec": {
-      "Ps": {
-        "replicas": 1,
-        "resources": "cpu=1,memory=1024M"
-      },
-      "Worker": {
-        "replicas": 1,
-        "resources": "cpu=1,memory=1024M"
-      }
-    },
-    "environment": {
-      "image": "gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0"
-    }
-  }
-}
diff --git a/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java
index 87d1c37..fd87e64 100644
--- a/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java
+++ b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java
@@ -20,6 +20,8 @@
 package org.apache.submarine.rest;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
 
 import javax.ws.rs.core.Response;
 
@@ -27,7 +29,15 @@ import org.apache.commons.httpclient.methods.DeleteMethod;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PostMethod;
 import org.apache.submarine.server.AbstractSubmarineServerTest;
+import org.apache.submarine.server.api.experiment.Experiment;
+import org.apache.submarine.server.api.experiment.ExperimentId;
 import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplate;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplateSubmit;
+import org.apache.submarine.server.api.spec.ExperimentSpec;
+import org.apache.submarine.server.api.spec.ExperimentTemplateParamSpec;
+import org.apache.submarine.server.api.spec.ExperimentTemplateSpec;
+import org.apache.submarine.server.gson.ExperimentIdDeserializer;
+import org.apache.submarine.server.gson.ExperimentIdSerializer;
 import org.apache.submarine.server.response.JsonResponse;
 import org.apache.submarine.server.rest.RestConstants;
 import org.junit.Assert;
@@ -36,14 +46,22 @@ import org.junit.Test;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
 
 @SuppressWarnings("rawtypes")
 public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerTest {
-
+  
   protected static String TPL_PATH =
       "/api/" + RestConstants.V1 + "/" + RestConstants.EXPERIMENT_TEMPLATES;
-  protected static String TPL_NAME = "tf-mnist-test2";
-
+  protected static String EXP_PATH =
+      "/api/" + RestConstants.V1 + "/" + RestConstants.EXPERIMENT;
+  protected static String TPL_NAME = "tf-mnist-test_1";
+  protected static String TPL_FILE = "experimentTemplate/test_template_1.json";
+  
+  private final Gson gson = new GsonBuilder()
+      .registerTypeAdapter(ExperimentId.class, new ExperimentIdSerializer())
+      .registerTypeAdapter(ExperimentId.class, new ExperimentIdDeserializer())
+      .create();
 
   @BeforeClass
   public static void startUp() throws Exception {
@@ -52,7 +70,7 @@ public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerT
 
   @Test
   public void testCreateExperimentTemplate() throws Exception {
-    String body = loadContent("experimenttemplate/test_template_2.json");
+    String body = loadContent(TPL_FILE);
     run(body, "application/json");
     deleteExperimentTemplate();
   }
@@ -60,10 +78,9 @@ public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerT
   @Test
   public void testGetExperimentTemplate() throws Exception {
 
-    String body = loadContent("experimenttemplate/test_template_2.json");
+    String body = loadContent(TPL_FILE);
     run(body, "application/json");
 
-    Gson gson = new GsonBuilder().create();
     GetMethod getMethod = httpGet(TPL_PATH + "/" + TPL_NAME);
     Assert.assertEquals(Response.Status.OK.getStatusCode(),
         getMethod.getStatusCode());
@@ -79,31 +96,47 @@ public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerT
     deleteExperimentTemplate();
   }
 
-
-  @Test
-  public void testUpdateExperimentTemplate() throws IOException {
-
-  }
-
   @Test
   public void testDeleteExperimentTemplate() throws Exception {
-    String body = loadContent("experimenttemplate/test_template_2.json");
+    LOG.info("testDeleteExperimentTemplate");
+
+    String body = loadContent(TPL_FILE);
     run(body, "application/json");
     deleteExperimentTemplate();
 
     GetMethod getMethod = httpGet(TPL_PATH + "/" + TPL_NAME);
     Assert.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
         getMethod.getStatusCode());
-
   }
 
   @Test
-  public void testListExperimentTemplates() throws IOException {
+  public void testListExperimentTemplates() throws Exception {
+    LOG.info("testListExperimentTemplates");
+
+    String body = loadContent(TPL_FILE);
+    run(body, "application/json");
+    
+    GetMethod getMethod = httpGet(TPL_PATH + "/");
+    Assert.assertEquals(Response.Status.OK.getStatusCode(),
+        getMethod.getStatusCode());
+
+    String json = getMethod.getResponseBodyAsString();
+    JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+    Assert.assertEquals(Response.Status.OK.getStatusCode(),
+        jsonResponse.getCode());
+
+    List<ExperimentTemplate> getExperimentTemplates =
+        gson.fromJson(gson.toJson(jsonResponse.getResult()), new TypeToken<List<ExperimentTemplate>>() {
+        }.getType());
+    
+    Assert.assertEquals(TPL_NAME, getExperimentTemplates.get(0).getExperimentTemplateSpec().getName());
 
+    deleteExperimentTemplate();
   }
 
   protected void deleteExperimentTemplate() throws IOException {
-    Gson gson = new GsonBuilder().create();
+
+    LOG.info("deleteExperimentTemplate");
     DeleteMethod deleteMethod = httpDelete(TPL_PATH + "/" + TPL_NAME);
     Assert.assertEquals(Response.Status.OK.getStatusCode(),
         deleteMethod.getStatusCode());
@@ -119,13 +152,9 @@ public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerT
   }
 
   protected void run(String body, String contentType) throws Exception {
-    Gson gson = new GsonBuilder().create();
 
-    // create
     LOG.info("Create ExperimentTemplate using ExperimentTemplate REST API");
-    LOG.info(body);
     PostMethod postMethod = httpPost(TPL_PATH, body, contentType);
-
     LOG.info(postMethod.getResponseBodyAsString());
 
     Assert.assertEquals(Response.Status.OK.getStatusCode(),
@@ -146,4 +175,54 @@ public class ExperimentTemplateManagerRestApiIT extends AbstractSubmarineServerT
     Assert.assertNotNull(tpl.getExperimentTemplateSpec().getName());
     Assert.assertNotNull(tpl.getExperimentTemplateSpec());
   }
+
+  @Test
+  public void submitExperimentTemplate() throws Exception {
+
+    String body = loadContent(TPL_FILE);
+    run(body, "application/json");   
+
+    ExperimentTemplateSpec tplspec = 
+    gson.fromJson(body, ExperimentTemplateSpec.class);
+    
+    String url = EXP_PATH + "/" + tplspec.getName();
+    LOG.info("Submit ExperimentTemplate using ExperimentTemplate REST API");
+    LOG.info(body);
+
+    ExperimentTemplateSubmit submit = new ExperimentTemplateSubmit();
+    submit.setParams(new HashMap<String, String>());
+    submit.setName(tplspec.getName());
+    for (ExperimentTemplateParamSpec parmSpec: tplspec.getExperimentTemplateParamSpec()) {
+      submit.getParams().put(parmSpec.getName(), parmSpec.getValue());
+    }
+    
+    PostMethod postMethod = httpPost(url, gson.toJson(submit), "application/json");
+    LOG.info(postMethod.getResponseBodyAsString());
+    Assert.assertEquals(Response.Status.OK.getStatusCode(), 
+        postMethod.getStatusCode());
+    
+    String json = postMethod.getResponseBodyAsString();
+    LOG.info(json);
+    JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+    Assert.assertEquals(Response.Status.OK.getStatusCode(),
+        jsonResponse.getCode());
+
+    deleteExperimentTemplate();
+    LOG.info(gson.toJson(jsonResponse.getResult()));
+    
+    Experiment experiment = gson.fromJson(gson.toJson(jsonResponse.getResult()), Experiment.class);
+
+    DeleteMethod deleteMethod = httpDelete("/api/" + RestConstants.V1 + "/" + RestConstants.EXPERIMENT + "/" 
+    + experiment.getExperimentId().toString());
+    Assert.assertEquals(Response.Status.OK.getStatusCode(), deleteMethod.getStatusCode());
+
+    json = deleteMethod.getResponseBodyAsString();
+    jsonResponse = gson.fromJson(json, JsonResponse.class);
+    Assert.assertEquals(Response.Status.OK.getStatusCode(), jsonResponse.getCode());
+    
+    ExperimentSpec tplExpSpec = tplspec.getExperimentSpec();
+    ExperimentSpec expSpec = experiment.getSpec();
+
+    Assert.assertEquals(tplExpSpec.getMeta().getName(), expSpec.getMeta().getName());
+  }
 }


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