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 517083e SUBMARINE-559. [API] Define Swagger API for pre-defined
template registration/delete, etc.
517083e is described below
commit 517083e79f0bbe723a585831faf74f45aeaee8ed
Author: JohnTing <[email protected]>
AuthorDate: Wed Aug 12 19:18:23 2020 +0800
SUBMARINE-559. [API] Define Swagger API for pre-defined template
registration/delete, etc.
### What is this PR for?
Make basic rest api for management experiment template.
get, post, del, patch
http://localhost/V1/template
### What type of PR is it?
[Bug Fix | Improvement | Feature | Documentation | Hot Fix | Refactoring]
### Todos
* [x] - REST API
* [x] - test
* [x] - e2e test
* [x] - parameter Mapping
### What is the Jira issue?
https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-559
### How should this be tested?
* First time? Setup Travis CI as described on
https://submarine.apache.org/contribution/contributions.html#continuous-integration
* Strongly recommended: add automated unit tests for any new or changed
behavior
* Outline any manual steps to test the PR here.
### Screenshots (if appropriate)






### 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 <[email protected]>
Closes #351 from JohnTing/SUBMARINE-559 and squashes the following commits:
6615be9 [JohnTing] chnge the name contained in the data
1c77b2b [JohnTing] Merge branch 'master' into SUBMARINE-559
3064b1e [JohnTing] done
096fe10 [JohnTing] parameterMapping
126cba7 [JohnTing] test2
6e53898 [JohnTing] test
2ac1b70 [JohnTing] submarine-559
f54a8e7 [JohnTing] add gitignore
---
.gitignore | 3 +
docs/database/submarine-data.sql | 6 +
docs/database/submarine.sql | 17 ++
.../api/experimenttemplate/ExperimentTemplate.java | 45 +++
.../experimenttemplate/ExperimentTemplateId.java | 72 +++++
.../api/spec/ExperimentTemplateParamSpec.java | 60 ++++
.../server/api/spec/ExperimentTemplateSpec.java | 70 +++++
.../ExperimentTemplateManager.java | 329 +++++++++++++++++++++
.../database/entity/ExperimentTemplateEntity.java | 46 +++
.../database/mappers/ExperimentTemplateMapper.java | 39 +++
.../server/rest/ExperimentTemplateRestApi.java | 194 ++++++++++++
.../submarine/server/rest/RestConstants.java | 7 +
.../src/main/resources/mybatis-config.xml | 1 +
.../database/mappers/ExperimentTemplateMapper.xml | 87 ++++++
.../server/rest/ExperimentTemplateRestApiTest.java | 144 +++++++++
.../experimenttemplate/test_template_1.json | 43 +++
.../experimenttemplate/test_template_2.json | 43 +++
.../rest/ExperimentTemplateManagerRestApiIT.java | 149 ++++++++++
.../workbench-web-ng/src/WEB-INF/web.xml | 3 +-
19 files changed, 1357 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 996243c..96e589b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,6 +95,9 @@ submarine-security/spark-security/derby.log
.classpath
.settings
.factorypath
+.vscode/settings.json
+.vscode/tasks.json
# jupyter notebook checkpoints
.ipynb_checkpoints
+
diff --git a/docs/database/submarine-data.sql b/docs/database/submarine-data.sql
index f63b581..1796638 100644
--- a/docs/database/submarine-data.sql
+++ b/docs/database/submarine-data.sql
@@ -85,3 +85,9 @@ INSERT INTO `params` (`id`, `key`, `value`, `worker_index`)
VALUES
-- Records of environment
-- ----------------------------
INSERT INTO `environment` VALUES ('environment_1595134205164_0002',
'my-submarine-test-env','{"name":"my-submarine-env","dockerImage":"continuumio/anaconda3","kernelSpec":{"name":"team_default_python_3.7","channels":["defaults"],"dependencies":["_ipyw_jlab_nb_ext_conf=0.1.0=py37_0","alabaster=0.7.12=py37_0","anaconda=2020.02=py37_0","anaconda-client=1.7.2=py37_0","anaconda-navigator=1.9.12=py37_0"]}}','admin',
'2020-05-06 14:00:05', 'Jack', '2020-05-06 14:00:14');
+
+-- ----------------------------
+-- Records of experiment_templates
+-- ----------------------------
+INSERT INTO `experiment_template` (`id`, `experimentTemplate_name`,
`experimentTemplate_spec`, `create_by`, `create_time`, `update_by`,
`update_time`) VALUES
+('experimentTemplate_1596391902149_0002', 'tf-mnist', '{\"name\":
\"tf-mnist\", \"author\": \"author0\", \"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\"}], \"description\": \"This
is a template to run tf-mnist\\n\", \"experimentSpec\": {\" [...]
diff --git a/docs/database/submarine.sql b/docs/database/submarine.sql
index d9ffa6f..76dfc49 100644
--- a/docs/database/submarine.sql
+++ b/docs/database/submarine.sql
@@ -262,3 +262,20 @@ CREATE TABLE `params` (
`worker_index` varchar(32) NOT NULL COMMENT '`String` (limit 32 characters).
Part of *Primary Key* for\r\n ``metrics`` table.',
PRIMARY KEY (`id`, `key`, `worker_index`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+-- ----------------------------
+-- Table structure for experiment_templates
+-- ----------------------------
+DROP TABLE IF EXISTS `experiment_template`;
+CREATE TABLE `experiment_template` (
+ `id` varchar(64) NOT NULL,
+ `experimentTemplate_name` varchar(32) NOT NULL,
+ `experimentTemplate_spec` json DEFAULT NULL,
+ `create_by` varchar(32) DEFAULT NULL,
+ `create_time` datetime NOT NULL,
+ `update_by` varchar(32) DEFAULT NULL,
+ `update_time` datetime NOT NULL,
+ PRIMARY KEY `id` (`id`),
+ UNIQUE KEY `experimentTemplate_name` (`experimentTemplate_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplate.java
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplate.java
new file mode 100644
index 0000000..3fb8b39
--- /dev/null
+++
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplate.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.apache.submarine.server.api.spec.ExperimentTemplateSpec;
+
+public class ExperimentTemplate {
+
+ private ExperimentTemplateId experimentTemplateId;
+
+ private ExperimentTemplateSpec experimentTemplateSpec;
+
+ public ExperimentTemplateId getExperimentTemplateId() {
+ return this.experimentTemplateId;
+ }
+
+ public void setExperimentTemplateId(ExperimentTemplateId
experimentTemplateId) {
+ this.experimentTemplateId = experimentTemplateId;
+ }
+
+ public ExperimentTemplateSpec getExperimentTemplateSpec() {
+ return this.experimentTemplateSpec;
+ }
+
+ public void setExperimentTemplateSpec(ExperimentTemplateSpec
experimentTemplateSpec) {
+ this.experimentTemplateSpec = experimentTemplateSpec;
+ }
+}
diff --git
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateId.java
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateId.java
new file mode 100644
index 0000000..2507a7e
--- /dev/null
+++
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/experimenttemplate/ExperimentTemplateId.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.apache.submarine.commons.utils.AbstractUniqueIdGenerator;
+
+/**
+ * The unique id for experimentTemplate. Formatter:
+ * experimentTemplate_${server_timestamp}_${counter} Such as:
+ * experimentTemplate_1577627710_0001
+ */
+public class ExperimentTemplateId extends
AbstractUniqueIdGenerator<ExperimentTemplateId> {
+
+ private static final String EXPERIMENT_ID_PREFIX = "experimentTemplate_";
+
+ /**
+ * Get the object of ExperimentTemplateId.
+ *
+ * @param experimentTemplateId Id of the experimentTemplate string
+ * @return object
+ */
+ public static ExperimentTemplateId fromString(String experimentTemplateId) {
+ if (experimentTemplateId == null) {
+ return null;
+ }
+ String[] components = experimentTemplateId.split("\\_");
+ if (components.length != 3) {
+ return null;
+ }
+ return ExperimentTemplateId.newInstance(Long.parseLong(components[1]),
+ Integer.parseInt(components[2]));
+ }
+
+ /**
+ * Ge the object of ExperimentTemplateId.
+ *
+ * @param serverTimestamp the timestamp when the server start
+ * @param id count
+ * @return object
+ */
+ public static ExperimentTemplateId newInstance(long serverTimestamp, int id)
{
+ ExperimentTemplateId experimentId = new ExperimentTemplateId();
+ experimentId.setServerTimestamp(serverTimestamp);
+ experimentId.setId(id);
+ return experimentId;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append(EXPERIMENT_ID_PREFIX).append(getServerTimestamp()).append("_");
+ format(sb, getId());
+ return sb.toString();
+ }
+}
diff --git
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateParamSpec.java
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateParamSpec.java
new file mode 100644
index 0000000..8a28c63
--- /dev/null
+++
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateParamSpec.java
@@ -0,0 +1,60 @@
+/*
+ * 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.spec;
+
+public class ExperimentTemplateParamSpec {
+
+ private String name;
+ private String required;
+ private String description;
+ private String value;
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getRequired() {
+ return this.required;
+ }
+
+ public void setRequired(String required) {
+ this.required = required;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
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
new file mode 100644
index 0000000..8dc5b58
--- /dev/null
+++
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/spec/ExperimentTemplateSpec.java
@@ -0,0 +1,70 @@
+/*
+ * 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.spec;
+
+import java.util.List;
+
+public class ExperimentTemplateSpec {
+ private String name;
+ private String author;
+ private String description;
+ private List<ExperimentTemplateParamSpec> parameters;
+ private ExperimentSpec experimentSpec;
+
+ public ExperimentSpec getExperimentSpec() {
+ return this.experimentSpec;
+ }
+
+ public void setExperimentSpec(ExperimentSpec experimentSpec) {
+ this.experimentSpec = experimentSpec;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAuthor() {
+ return this.author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List<ExperimentTemplateParamSpec> getParameters() {
+ return this.parameters;
+ }
+
+ public void setParameters(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
new file mode 100644
index 0000000..feac673
--- /dev/null
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/ExperimentTemplateManager.java
@@ -0,0 +1,329 @@
+/*
+ * 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.experimenttemplate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.experimenttemplate.ExperimentTemplate;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplateId;
+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.experimenttemplate.database.entity.ExperimentTemplateEntity;
+import
org.apache.submarine.server.experimenttemplate.database.mappers.ExperimentTemplateMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * ExperimentTemplate Management
+ */
+public class ExperimentTemplateManager {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ExperimentTemplateManager.class);
+
+ private static volatile ExperimentTemplateManager manager;
+
+ private final AtomicInteger experimentTemplateIdCounter = new
AtomicInteger(0);
+
+ /**
+ * ExperimentTemplate Cache
+ */
+ private final ConcurrentMap<String, ExperimentTemplate>
cachedExperimentTemplates =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Get the singleton instance
+ *
+ * @return object
+ */
+ public static ExperimentTemplateManager getInstance() {
+ if (manager == null) {
+ synchronized (ExperimentTemplateManager.class) {
+ if (manager == null) {
+ manager = new ExperimentTemplateManager();
+ }
+ }
+ }
+ return manager;
+ }
+
+ private ExperimentTemplateManager() {
+
+ }
+
+ /**
+ * Create ExperimentTemplate
+ *
+ * @param spec experimentTemplate spec
+ * @return ExperimentTemplate experimentTemplate
+ * @throws SubmarineRuntimeException the service error
+ */
+ public ExperimentTemplate createExperimentTemplate(ExperimentTemplateSpec
spec)
+ throws SubmarineRuntimeException {
+ checkSpec(spec);
+ LOG.info("Create ExperimentTemplate using spec: " + spec.toString());
+ return createOrUpdateExperimentTemplate(spec, "c");
+ }
+
+ /**
+ * Update experimentTemplate
+ *
+ * @param name Name of the experimentTemplate
+ * @param spec experimentTemplate spec
+ * @return ExperimentTemplate experimentTemplate
+ * @throws SubmarineRuntimeException the service error
+ */
+ public ExperimentTemplate updateExperimentTemplate(String name,
ExperimentTemplateSpec spec)
+ throws SubmarineRuntimeException {
+ ExperimentTemplate tpl = getExperimentTemplateDetails(name);
+ if (tpl == null) {
+ throw new SubmarineRuntimeException(Status.NOT_FOUND.getStatusCode(),
"ExperimentTemplate not found.");
+ }
+ checkSpec(spec);
+ LOG.info("Update ExperimentTemplate using spec: " + spec.toString());
+ return createOrUpdateExperimentTemplate(spec, "u");
+ }
+
+ private ExperimentTemplate
createOrUpdateExperimentTemplate(ExperimentTemplateSpec spec, String operation)
{
+ ExperimentTemplateEntity entity = new ExperimentTemplateEntity();
+ String experimentTemplateId = generateExperimentTemplateId().toString();
+ entity.setId(experimentTemplateId);
+ entity.setExperimentTemplateName(spec.getName());
+ entity.setExperimentTemplateSpec(new
GsonBuilder().disableHtmlEscaping().create().toJson(spec));
+
+ parameterMapping(entity.getExperimentTemplateSpec());
+
+ try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+ ExperimentTemplateMapper experimentTemplateMapper =
+ sqlSession.getMapper(ExperimentTemplateMapper.class);
+
+ if (operation.equals("c")) {
+ experimentTemplateMapper.insert(entity);
+ } else {
+ experimentTemplateMapper.update(entity);
+ }
+ sqlSession.commit();
+
+ ExperimentTemplate 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.");
+ }
+ }
+
+ private ExperimentTemplateId generateExperimentTemplateId() {
+ return
ExperimentTemplateId.newInstance(SubmarineServer.getServerTimeStamp(),
+ experimentTemplateIdCounter.incrementAndGet());
+ }
+
+ /**
+ * Delete experimentTemplate
+ *
+ * @param name Name of the experimentTemplate
+ * @return ExperimentTemplate experimentTemplate
+ * @throws SubmarineRuntimeException the service error
+ */
+ public ExperimentTemplate deleteExperimentTemplate(String name) throws
SubmarineRuntimeException {
+ ExperimentTemplate tpl = getExperimentTemplateDetails(name);
+ if (tpl == null) {
+ throw new SubmarineRuntimeException(Status.NOT_FOUND.getStatusCode(),
"ExperimentTemplate not found.");
+ }
+
+ LOG.info("Delete ExperimentTemplate for " + name);
+ try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+ ExperimentTemplateMapper experimentTemplateMapper =
+ sqlSession.getMapper(ExperimentTemplateMapper.class);
+
+ experimentTemplateMapper.delete(name);
+ sqlSession.commit();
+
+ // Invalidate cache
+ cachedExperimentTemplates.remove(name);
+ return tpl;
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Unable to delete the experimentTemplate.");
+ }
+ }
+
+ /**
+ * Get ExperimentTemplate
+ *
+ * @param name Name of the experimentTemplate
+ * @return ExperimentTemplate experimentTemplate
+ * @throws SubmarineRuntimeException the service error
+ */
+ public ExperimentTemplate getExperimentTemplate(String name) throws
SubmarineRuntimeException {
+ ExperimentTemplate experimentTemplate = getExperimentTemplateDetails(name);
+ if (experimentTemplate == null) {
+ throw new SubmarineRuntimeException(Status.NOT_FOUND.getStatusCode(),
"ExperimentTemplate not found.");
+ }
+ return experimentTemplate;
+ }
+
+ /**
+ * List experimentTemplates
+ *
+ * @param status experimentTemplate status, if null will return all status
+ * @return experimentTemplate list
+ * @throws SubmarineRuntimeException the service error
+ */
+ public List<ExperimentTemplate> listExperimentTemplates(String status)
throws SubmarineRuntimeException {
+ List<ExperimentTemplate> tpls = new
ArrayList<>(cachedExperimentTemplates.values());
+
+ // Is it available in cache?
+ if (tpls != null && tpls.size() != 0) {
+ return tpls;
+ }
+ try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+ ExperimentTemplateMapper experimentTemplateMapper =
+ sqlSession.getMapper(ExperimentTemplateMapper.class);
+
+ List<ExperimentTemplateEntity> experimentTemplateEntitys =
experimentTemplateMapper.selectByKey(null);
+ for (ExperimentTemplateEntity experimentTemplateEntity :
experimentTemplateEntitys) {
+ if (experimentTemplateEntity != null) {
+ ExperimentTemplate tpl = new ExperimentTemplate();
+
+ tpl.setExperimentTemplateSpec(
+ new
Gson().fromJson(experimentTemplateEntity.getExperimentTemplateSpec(),
+ ExperimentTemplateSpec.class));
+ tpls.add(tpl);
+
cachedExperimentTemplates.put(tpl.getExperimentTemplateSpec().getName(), tpl);
+ }
+ }
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Unable to get the experimentTemplate details.");
+ }
+ return tpls;
+ }
+
+ private void checkSpec(ExperimentTemplateSpec spec) throws
SubmarineRuntimeException {
+ if (spec == null) {
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Invalid experimentTemplate spec.");
+ }
+ }
+
+ private ExperimentTemplate getExperimentTemplateDetails(String name) throws
SubmarineRuntimeException {
+
+ // Is it available in cache?
+ ExperimentTemplate tpl = cachedExperimentTemplates.get(name);
+ if (tpl != null) {
+ return tpl;
+ }
+ ExperimentTemplateEntity experimentTemplateEntity;
+ try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+ ExperimentTemplateMapper experimentTemplateMapper =
+ sqlSession.getMapper(ExperimentTemplateMapper.class);
+
+ experimentTemplateEntity = experimentTemplateMapper.select(name);
+
+ if (experimentTemplateEntity != null) {
+ tpl = new ExperimentTemplate();
+ tpl.setExperimentTemplateSpec(
+ new
Gson().fromJson(experimentTemplateEntity.getExperimentTemplateSpec(),
+ ExperimentTemplateSpec.class));
+ }
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Unable to get the experimentTemplate details.");
+ }
+ return tpl;
+ }
+
+ 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()) {
+ if (parm.getValue() != null) {
+ parmMap.put(parm.getName(), parm.getValue());
+ } else {
+ parmMap.put(parm.getName(), "");
+ }
+ }
+
+ Pattern pattern = Pattern.compile("\\{\\{(.+?)\\}\\}");
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = pattern.matcher(spec);
+
+ List<String> unmappedKeys = new ArrayList<String>();
+
+ while (matcher.find()) {
+ final String key = matcher.group(1);
+ final String replacement = parmMap.get(key);
+ if (replacement == null) {
+ unmappedKeys.add(key);
+ }
+ else {
+ matcher.appendReplacement(sb, replacement);
+ }
+ parmMap.remove(key);
+ }
+ matcher.appendTail(sb);
+
+ if (parmMap.size() > 0) {
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Parameters contains unused key: " + parmMap.keySet());
+ }
+
+ if (unmappedKeys.size() > 0) {
+ throw new SubmarineRuntimeException(Status.BAD_REQUEST.getStatusCode(),
+ "Template contains unmapped key: " + unmappedKeys);
+ }
+
+ try {
+ tplSpec = 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;
+ }
+}
diff --git
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/entity/ExperimentTemplateEntity.java
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/entity/ExperimentTemplateEntity.java
new file mode 100644
index 0000000..55cb338
--- /dev/null
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/entity/ExperimentTemplateEntity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.experimenttemplate.database.entity;
+
+import org.apache.submarine.server.database.entity.BaseEntity;
+
+public class ExperimentTemplateEntity extends BaseEntity {
+
+ private String experimentTemplateName;
+
+ private String experimentTemplateSpec;
+
+ public String getExperimentTemplateName() {
+ return this.experimentTemplateName;
+ }
+
+ public void setExperimentTemplateName(String ExperimentTemplateName) {
+ this.experimentTemplateName = ExperimentTemplateName;
+ }
+
+ public String getExperimentTemplateSpec() {
+ return this.experimentTemplateSpec;
+ }
+
+ public void setExperimentTemplateSpec(String ExperimentTemplateSpec) {
+ this.experimentTemplateSpec = ExperimentTemplateSpec;
+ }
+
+}
diff --git
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/mappers/ExperimentTemplateMapper.java
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/mappers/ExperimentTemplateMapper.java
new file mode 100644
index 0000000..c0646ce
--- /dev/null
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experimenttemplate/database/mappers/ExperimentTemplateMapper.java
@@ -0,0 +1,39 @@
+
+/*
+ * 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.experimenttemplate.database.mappers;
+
+import java.util.List;
+
+import
org.apache.submarine.server.experimenttemplate.database.entity.ExperimentTemplateEntity;
+
+
+public interface ExperimentTemplateMapper {
+
+ ExperimentTemplateEntity select(String ExperimentTemplateName);
+
+ int insert(ExperimentTemplateEntity ExperimentTemplate);
+
+ int update(ExperimentTemplateEntity ExperimentTemplate);
+
+ int delete(String ExperimentTemplateName);
+
+ List<ExperimentTemplateEntity> selectByKey(ExperimentTemplateEntity
ExperimentTemplate);
+
+}
diff --git
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentTemplateRestApi.java
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentTemplateRestApi.java
new file mode 100644
index 0000000..a20c536
--- /dev/null
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ExperimentTemplateRestApi.java
@@ -0,0 +1,194 @@
+/*
+ * 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.rest;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplate;
+import org.apache.submarine.server.api.spec.ExperimentTemplateSpec;
+import
org.apache.submarine.server.experimenttemplate.ExperimentTemplateManager;
+import org.apache.submarine.server.response.JsonResponse;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+/**
+ * ExperimentTemplate REST API v1. It can accept {@link
ExperimentTemplateSpec} to create a
+ * experimentTemplate.
+ */
+@Path(RestConstants.V1 + "/" + RestConstants.EXPERIMENT_TEMPLATES)
+@Produces({MediaType.APPLICATION_JSON + "; " + RestConstants.CHARSET_UTF8})
+public class ExperimentTemplateRestApi {
+ private final ExperimentTemplateManager experimentTemplateManager =
+ ExperimentTemplateManager.getInstance();
+
+ /**
+ * Returns the contents of {@link experimentTemplate}.
+ * @param spec experimentTemplate spec
+ * @return the contents of experimentTemplate
+ */
+ @POST
+ @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
+ @Operation(summary = "Create a experimentTemplate",
+ tags = {"experimentTemplate"},
+ responses = {
+ @ApiResponse(description = "successful operation",
+ content = @Content(
+ schema = @Schema(
+ implementation = JsonResponse.class)))})
+ public Response createExperimentTemplate(ExperimentTemplateSpec spec) {
+ try {
+ ExperimentTemplate experimentTemplate =
experimentTemplateManager.createExperimentTemplate(spec);
+ return new JsonResponse.Builder<ExperimentTemplate>(Response.Status.OK)
+ .success(true).result(experimentTemplate).build();
+ } catch (SubmarineRuntimeException e) {
+ return parseExperimentTemplateServiceException(e);
+ }
+ }
+
+ /**
+ * Update experimentTemplate.
+ * @param name Name of the experimentTemplate
+ * @param spec experimentTemplate spec
+ * @return the detailed info about updated experimentTemplate
+ */
+ @PATCH
+ @Path("/{id}")
+ @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
+ @Operation(summary = "Update the experimentTemplate with job spec",
+ tags = {"experimentTemplates"},
+ responses = {
+ @ApiResponse(description = "successful operation",
+ content = @Content(
+ schema = @Schema(
+ implementation = JsonResponse.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "ExperimentTemplate not found")})
+ public Response updateExperimentTemplate(
+ @PathParam(RestConstants.EXPERIMENT_TEMPLATE_ID) String name,
+ ExperimentTemplateSpec spec) {
+ try {
+ ExperimentTemplate experimentTemplate =
+ experimentTemplateManager.updateExperimentTemplate(name, spec);
+ return new JsonResponse.Builder<ExperimentTemplate>(Response.Status.OK)
+ .success(true).result(experimentTemplate).build();
+ } catch (SubmarineRuntimeException e) {
+ return parseExperimentTemplateServiceException(e);
+ }
+ }
+
+ /**
+ * Returns the experimentTemplate that deleted.
+ * @param name Name of the experimentTemplate
+ * @return the detailed info about deleted experimentTemplate
+ */
+ @DELETE
+ @Path("/{id}")
+ @Operation(summary = "Delete the experimentTemplate",
+ tags = {"experimentTemplates"},
+ responses = {
+ @ApiResponse(description = "successful operation",
+ content = @Content(
+ schema = @Schema(implementation =
JsonResponse.class))),
+ @ApiResponse(
+ responseCode = "404", description = "ExperimentTemplate
not found")})
+ public Response deleteExperimentTemplate(
+ @PathParam(RestConstants.EXPERIMENT_TEMPLATE_ID) String name) {
+ try {
+ ExperimentTemplate experimentTemplate =
experimentTemplateManager.deleteExperimentTemplate(name);
+ return new JsonResponse.Builder<ExperimentTemplate>(Response.Status.OK)
+ .success(true).result(experimentTemplate).build();
+ } catch (SubmarineRuntimeException e) {
+ return parseExperimentTemplateServiceException(e);
+ }
+ }
+
+ /**
+ * List all experimentTemplates.
+ * @return experimentTemplate list
+ */
+ @GET
+ @Operation(summary = "List of ExperimentTemplates",
+ tags = {"experimentTemplates"},
+ responses = {
+ @ApiResponse(description = "successful operation",
+ content = @Content(
+ schema = @Schema(
+ implementation = JsonResponse.class)))})
+ public Response listExperimentTemplate(@QueryParam("status") String status) {
+ try {
+ List<ExperimentTemplate> experimentTemplateList =
+ experimentTemplateManager.listExperimentTemplates(status);
+ return new
JsonResponse.Builder<List<ExperimentTemplate>>(Response.Status.OK)
+ .success(true).result(experimentTemplateList).build();
+ } catch (SubmarineRuntimeException e) {
+ return parseExperimentTemplateServiceException(e);
+ }
+ }
+
+ /**
+ * Returns details for the given experimentTemplate.
+ * @param name Name of the experimentTemplate
+ * @return the contents of experimentTemplate
+ */
+ @GET
+ @Path("/{id}")
+ @Operation(summary = "Find experimentTemplate by name",
+ tags = {"experimentTemplate"},
+ responses = {
+ @ApiResponse(description = "successful operation",
+ content = @Content(
+ schema = @Schema(implementation =
JsonResponse.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "ExperimentTemplate not found")})
+ public Response getExperimentTemplate(
+ @PathParam(RestConstants.EXPERIMENT_TEMPLATE_ID) String name) {
+ try {
+ ExperimentTemplate experimentTemplate =
experimentTemplateManager.getExperimentTemplate(name);
+ return new JsonResponse.Builder<ExperimentTemplate>(Response.Status.OK)
+ .success(true).result(experimentTemplate).build();
+ } catch (SubmarineRuntimeException e) {
+ return parseExperimentTemplateServiceException(e);
+ }
+ }
+
+ private Response parseExperimentTemplateServiceException(
+ SubmarineRuntimeException e) {
+ return new
JsonResponse.Builder<String>(e.getCode()).message(e.getMessage())
+ .build();
+ }
+}
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 ecfce40..96e67f5 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
@@ -45,6 +45,13 @@ public class RestConstants {
public static final String ENVIRONMENT_ID = "id";
/**
+ * Experimect template
+ */
+ public static final String EXPERIMENT_TEMPLATES = "template";
+
+ public static final String EXPERIMENT_TEMPLATE_ID = "id";
+
+ /**
* Notebook
*/
public static final String NOTEBOOK = "notebook";
diff --git a/submarine-server/server-core/src/main/resources/mybatis-config.xml
b/submarine-server/server-core/src/main/resources/mybatis-config.xml
index fee836c..06e4991 100755
--- a/submarine-server/server-core/src/main/resources/mybatis-config.xml
+++ b/submarine-server/server-core/src/main/resources/mybatis-config.xml
@@ -67,5 +67,6 @@
<mapper resource='org/apache/submarine/database/mappers/MetricMapper.xml'/>
<mapper resource='org/apache/submarine/database/mappers/ParamMapper.xml'/>
<mapper
resource='org/apache/submarine/database/mappers/EnvironmentMapper.xml'/>
+ <mapper
resource='org/apache/submarine/database/mappers/ExperimentTemplateMapper.xml'/>
</mappers>
</configuration>
diff --git
a/submarine-server/server-core/src/main/resources/org/apache/submarine/database/mappers/ExperimentTemplateMapper.xml
b/submarine-server/server-core/src/main/resources/org/apache/submarine/database/mappers/ExperimentTemplateMapper.xml
new file mode 100644
index 0000000..9643b57
--- /dev/null
+++
b/submarine-server/server-core/src/main/resources/org/apache/submarine/database/mappers/ExperimentTemplateMapper.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper
namespace="org.apache.submarine.server.experimenttemplate.database.mappers.ExperimentTemplateMapper">
+ <resultMap id="BaseEntityResultMap"
type="org.apache.submarine.server.database.entity.BaseEntity">
+ <id property="id" column="id"/>
+ <result column="create_by" property="createBy"/>
+ <result column="create_time" property="createTime"/>
+ <result column="update_by" property="updateBy"/>
+ <result column="update_time" property="updateTime"/>
+ </resultMap>
+
+ <resultMap id="resultMap"
type="org.apache.submarine.server.experimenttemplate.database.entity.ExperimentTemplateEntity"
extends="BaseEntityResultMap">
+ <result column="experimentTemplate_name" jdbcType="VARCHAR"
property="experimentTemplateName" />
+ <result column="experimentTemplate_spec" jdbcType="VARCHAR"
property="experimentTemplateSpec" />
+ </resultMap>
+
+ <sql id="Base_Column_List">
+ id, experimentTemplate_name, experimentTemplate_spec, create_by,
create_time, update_by, update_time
+ </sql>
+
+ <select id="select" parameterType="java.lang.String" resultMap="resultMap">
+ select
+ <include refid="Base_Column_List" />
+ from experiment_template
+ where experimentTemplate_name = #{experimentTemplate_name,jdbcType=VARCHAR}
+ </select>
+
+ <delete id="delete" parameterType="java.lang.String">
+ delete from experiment_template
+ where experimentTemplate_name = #{experimentTemplate_name,jdbcType=VARCHAR}
+ </delete>
+
+ <insert id="insert"
parameterType="org.apache.submarine.server.experimenttemplate.database.entity.ExperimentTemplateEntity">
+ insert into experiment_template (id, experimentTemplate_name,
experimentTemplate_spec,
+ create_by, create_time, update_by, update_time)
+ values (#{id,jdbcType=VARCHAR},
+ #{experimentTemplateName,jdbcType=VARCHAR},
+ #{experimentTemplateSpec,jdbcType=VARCHAR},
+ #{createBy,jdbcType=VARCHAR}, now(), #{updateBy,jdbcType=VARCHAR}, now())
+ </insert>
+
+ <update id="update"
parameterType="org.apache.submarine.server.experimenttemplate.database.entity.ExperimentTemplateEntity">
+ update experiment_template
+ <set>
+ <if test="experimentTemplateSpec != null">
+ experimentTemplate_spec = #{experimentTemplateSpec,jdbcType=VARCHAR},
+ </if>
+ update_time = now()
+ </set>
+ where experimentTemplate_name = #{experimentTemplateName,jdbcType=VARCHAR}
+ </update>
+
+ <select id="selectByKey" parameterType="java.lang.String"
resultMap="resultMap">
+ select
+ <include refid="Base_Column_List" />
+ from experiment_template
+ where 1 = 1
+ <if test="id != null">
+ AND id = #{id,jdbcType=VARCHAR}
+ </if>
+ <if test="experimentTemplateName != null">
+ AND experimentTemplate_name = #{experimentTemplateName,jdbcType=VARCHAR}
+ </if>
+ <!-- AND JSON_EXTRACT(`experimentTemplate_spec`, "$.name") == -->
+
+
+ </select>
+
+</mapper>
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
new file mode 100644
index 0000000..1813609
--- /dev/null
+++
b/submarine-server/server-core/src/test/java/org/apache/submarine/server/rest/ExperimentTemplateRestApiTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.rest;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import org.apache.submarine.commons.utils.SubmarineConfiguration;
+import org.apache.submarine.server.api.experimenttemplate.ExperimentTemplate;
+import org.apache.submarine.server.api.spec.ExperimentTemplateSpec;
+import org.apache.submarine.server.response.JsonResponse;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+
+import static org.junit.Assert.assertEquals;
+
+public class ExperimentTemplateRestApiTest {
+ private static ExperimentTemplateRestApi experimentTemplateStoreApi;
+ private static ExperimentTemplateSpec experimentTemplateSpec;
+
+ private static GsonBuilder gsonBuilder = new GsonBuilder();
+ private static Gson gson = gsonBuilder.setDateFormat("yyyy-MM-dd
HH:mm:ss").create();
+
+ @BeforeClass
+ public static void init() {
+ SubmarineConfiguration submarineConf =
SubmarineConfiguration.getInstance();
+
submarineConf.setMetastoreJdbcUrl("jdbc:mysql://127.0.0.1:3306/submarine_test?"
+ "useUnicode=true&"
+ + "characterEncoding=UTF-8&" + "autoReconnect=true&" +
"failOverReadOnly=false&"
+ + "zeroDateTimeBehavior=convertToNull&" + "useSSL=false");
+ submarineConf.setMetastoreJdbcUserName("submarine_test");
+ submarineConf.setMetastoreJdbcPassword("password_test");
+ experimentTemplateStoreApi = new ExperimentTemplateRestApi();
+ }
+
+ @Before
+ public void createAndUpdateExperimentTemplate() {
+ String body = loadContent("experimenttemplate/test_template_1.json");
+ 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.
+ updateExperimentTemplate(experimentTemplateSpec.getName(),
experimentTemplateSpec);
+ assertEquals(Response.Status.OK.getStatusCode(),
updateTplResponse.getStatus());
+ }
+
+ @After
+ public void deleteExperimentTemplate() {
+
+ String body = loadContent("experimenttemplate/test_template_1.json");
+ experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
+
+ Response deleteEnvResponse = experimentTemplateStoreApi.
+ deleteExperimentTemplate(experimentTemplateSpec.getName());
+ assertEquals(Response.Status.OK.getStatusCode(),
deleteEnvResponse.getStatus());
+ }
+
+ @Test
+ public void getExperimentTemplate() {
+
+ String body = loadContent("experimenttemplate/test_template_1.json");
+ experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
+
+ Response getEnvResponse = experimentTemplateStoreApi.
+ getExperimentTemplate(experimentTemplateSpec.getName());
+ ExperimentTemplate experimentTemplate =
getExperimentTemplateFromResponse(getEnvResponse);
+ assertEquals(experimentTemplateSpec.getName(),
experimentTemplate.getExperimentTemplateSpec().getName());
+
+ }
+
+ private ExperimentTemplate getExperimentTemplateFromResponse(Response
response) {
+ String entity = (String) response.getEntity();
+ Type type = new TypeToken<JsonResponse<ExperimentTemplate>>() {
+ }.getType();
+ JsonResponse<ExperimentTemplate> jsonResponse = gson.fromJson(entity,
type);
+ return jsonResponse.getResult();
+ }
+
+ @Test
+ public void listExperimentTemplate() {
+
+ String body = loadContent("experimenttemplate/test_template_1.json");
+ experimentTemplateSpec = gson.fromJson(body, ExperimentTemplateSpec.class);
+
+ Response getEnvResponse =
experimentTemplateStoreApi.listExperimentTemplate("");
+ String entity = (String) getEnvResponse.getEntity();
+ JsonResponse jsonResponse = gson.fromJson(entity, JsonResponse.class);
+ ExperimentTemplate[] experimentTemplates =
gson.fromJson(gson.toJson(jsonResponse.getResult()),
+ ExperimentTemplate[].class);
+ assertEquals(1, experimentTemplates.length);
+
+ ExperimentTemplate experimentTemplate = experimentTemplates[0];
+ assertEquals(experimentTemplateSpec.getName(),
experimentTemplate.getExperimentTemplateSpec().getName());
+ }
+
+ protected String loadContent(String resourceName) {
+ StringBuilder content = new StringBuilder();
+ InputStream inputStream =
+ this.getClass().getClassLoader().getResourceAsStream(resourceName);
+ BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
+ String l;
+ try {
+ while ((l = r.readLine()) != null) {
+ content.append(l).append("\n");
+ }
+ inputStream.close();
+ } catch (IOException e) {
+ Assert.fail(e.toString());
+ }
+ return content.toString();
+ }
+}
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
new file mode 100644
index 0000000..61448d5
--- /dev/null
+++
b/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_1.json
@@ -0,0 +1,43 @@
+{
+ "name": "tf-mnist-test",
+ "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-server/server-core/src/test/resources/experimenttemplate/test_template_2.json
b/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_2.json
new file mode 100644
index 0000000..0c8cae1
--- /dev/null
+++
b/submarine-server/server-core/src/test/resources/experimenttemplate/test_template_2.json
@@ -0,0 +1,43 @@
+{
+ "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
new file mode 100644
index 0000000..87d1c37
--- /dev/null
+++
b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/ExperimentTemplateManagerRestApiIT.java
@@ -0,0 +1,149 @@
+/*
+ * 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.rest;
+
+import java.io.IOException;
+
+import javax.ws.rs.core.Response;
+
+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.experimenttemplate.ExperimentTemplate;
+import org.apache.submarine.server.response.JsonResponse;
+import org.apache.submarine.server.rest.RestConstants;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+@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";
+
+
+ @BeforeClass
+ public static void startUp() throws Exception {
+ Assert.assertTrue(checkIfServerIsRunning());
+ }
+
+ @Test
+ public void testCreateExperimentTemplate() throws Exception {
+ String body = loadContent("experimenttemplate/test_template_2.json");
+ run(body, "application/json");
+ deleteExperimentTemplate();
+ }
+
+ @Test
+ public void testGetExperimentTemplate() throws Exception {
+
+ String body = loadContent("experimenttemplate/test_template_2.json");
+ run(body, "application/json");
+
+ Gson gson = new GsonBuilder().create();
+ GetMethod getMethod = httpGet(TPL_PATH + "/" + TPL_NAME);
+ 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());
+
+ ExperimentTemplate getExperimentTemplate =
+ gson.fromJson(gson.toJson(jsonResponse.getResult()),
ExperimentTemplate.class);
+ Assert.assertEquals(TPL_NAME,
getExperimentTemplate.getExperimentTemplateSpec().getName());
+ deleteExperimentTemplate();
+ }
+
+
+ @Test
+ public void testUpdateExperimentTemplate() throws IOException {
+
+ }
+
+ @Test
+ public void testDeleteExperimentTemplate() throws Exception {
+ String body = loadContent("experimenttemplate/test_template_2.json");
+ 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 {
+
+ }
+
+ protected void deleteExperimentTemplate() throws IOException {
+ Gson gson = new GsonBuilder().create();
+ DeleteMethod deleteMethod = httpDelete(TPL_PATH + "/" + TPL_NAME);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ deleteMethod.getStatusCode());
+
+ String json = deleteMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ jsonResponse.getCode());
+
+ ExperimentTemplate deletedTpl =
+ gson.fromJson(gson.toJson(jsonResponse.getResult()),
ExperimentTemplate.class);
+ Assert.assertEquals(TPL_NAME,
deletedTpl.getExperimentTemplateSpec().getName());
+ }
+
+ 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(),
+ postMethod.getStatusCode());
+
+ String json = postMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ jsonResponse.getCode());
+
+ ExperimentTemplate tpl =
+ gson.fromJson(gson.toJson(jsonResponse.getResult()),
ExperimentTemplate.class);
+ verifyCreateExperimentTemplateApiResult(tpl);
+ }
+
+ protected void verifyCreateExperimentTemplateApiResult(ExperimentTemplate
tpl)
+ throws Exception {
+ Assert.assertNotNull(tpl.getExperimentTemplateSpec().getName());
+ Assert.assertNotNull(tpl.getExperimentTemplateSpec());
+ }
+}
diff --git a/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
b/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
index b969edd..e4050f5 100644
--- a/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
+++ b/submarine-workbench/workbench-web-ng/src/WEB-INF/web.xml
@@ -32,7 +32,8 @@
</init-param>
<init-param>
<param-name>openApi.configuration.resourceClasses</param-name>
-
<param-value>org.apache.submarine.server.rest.ExperimentRestApi</param-value>
+ <param-value>org.apache.submarine.server.rest.ExperimentRestApi,
+ org.apache.submarine.server.rest.ExperimentTemplateRestApi</param-value>
</init-param>
<init-param>
<param-name>openApi.configuration.scannerClass</param-name>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]