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

SUBMARINE-558. Define Swagger API for pre-defined template submission #382

Closed
wants to merge 15 commits into from
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