Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Commit

Permalink
SUBMARINE-558. Define Swagger API for pre-defined template submission
Browse files Browse the repository at this point in the history
### 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 <jot.johnting@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
  • Loading branch information
JohnTing authored and xunliu committed Sep 12, 2020
1 parent 81a9ff0 commit 57eb259
Show file tree
Hide file tree
Showing 10 changed files with 415 additions and 95 deletions.
142 changes: 142 additions & 0 deletions docs/userdocs/k8s/use-experiment-template.md
Original file line number Diff line number Diff line change
@@ -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
```
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,12 @@ public List<ExperimentTemplateParamSpec> getParameters() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@
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;

Expand Down Expand Up @@ -137,19 +141,27 @@ private ExperimentTemplate createOrUpdateExperimentTemplate(ExperimentTemplateSp
}
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() {
Expand Down Expand Up @@ -276,19 +288,59 @@ private ExperimentTemplate getExperimentTemplateDetails(String name) throws Subm
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);
Expand All @@ -297,33 +349,34 @@ private ExperimentTemplateSpec parameterMapping(String spec) {

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;
}
}
Loading

0 comments on commit 57eb259

Please sign in to comment.