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

pingsutw 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 12f65ef  SUBMARINE-1060. Create TensorFlow model serving
12f65ef is described below

commit 12f65efa60db3f26ff7a86a81ea8f911d56fa14d
Author: KUAN-HSUN-LI <[email protected]>
AuthorDate: Mon Nov 15 14:52:06 2021 +0800

    SUBMARINE-1060. Create TensorFlow model serving
    
    ### What is this PR for?
    1. Use `seldon core` to create the TensorFlow model serving.
    2. Create model serve rest API.
    3. **Inference API is not created in this PR**
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/SUBMARINE-1060
    
    ### How should this be tested?
    The test will be provided after the `minIO` client is created.
    
    ### Screenshots (if appropriate)
    
https://user-images.githubusercontent.com/38066413/141690103-6a3b9c36-4feb-4295-8bcd-3914c9736769.mp4
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? Yes
    * Does this need new documentation? Yes
    
    Author: KUAN-HSUN-LI <[email protected]>
    
    Signed-off-by: Kevin <[email protected]>
    
    Closes #800 from KUAN-HSUN-LI/SUBMARINE-1060 and squashes the following 
commits:
    
    d2658285 [KUAN-HSUN-LI] SUBMARINE-1060. Create TensorFlow Serving
    ade6149b [KUAN-HSUN-LI] adjustment
    ae95b1a1 [KUAN-HSUN-LI] add document
    d7fba1cb [KUAN-HSUN-LI] SUBMARINE-1060. Add error handle and License
    97504bd5 [KUAN-HSUN-LI] SUBMARINE-1060. Implement Delete Serve
    f80285a8 [KUAN-HSUN-LI] SUBMARINE-1060. Implement Create Serve
    19f99fd5 [KUAN-HSUN-LI] SUBMARINE-1060. Create TensorFlow Serving
    5f3f9d36 [KUAN-HSUN-LI] SUBMARINE-1060. Create TensorFlow Serving
    40c85c0c [KUAN-HSUN-LI] setup
---
 helm-charts/submarine/templates/rbac.yaml          |  13 +++
 pom.xml                                            |   1 +
 .../artifacts/submarine/submarine-rbac.yaml        |  13 +++
 .../submitter-k8s => submarine-serve}/pom.xml      |  56 ++-------
 .../submarine/serve/seldon/SeldonDeployment.java   | 126 +++++++++++++++++++++
 .../apache/submarine/serve/seldon/SeldonGraph.java |  59 ++++++++++
 .../submarine/serve/seldon/SeldonPredictor.java    |  64 +++++++++++
 .../serve/tensorflow/SeldonTFServing.java          |  45 ++++++++
 .../submarine/serve/utils/SeldonConstants.java     |  42 +++++++
 .../src/main/resources/log4j.properties            |  25 ++++
 .../serve/tensorflow/SeldonTFServingTest.java      |  22 ++++
 .../org/apache/submarine/server/api/Submitter.java |  13 +--
 .../submarine/server/api/model/ServeResponse.java  |  31 +++++
 .../submarine/server/api/model/ServeSpec.java      |  58 ++++++++++
 .../server/experiment/ExperimentManager.java       |  28 -----
 .../submarine/server/model/ModelManager.java       | 110 ++++++++++++++++++
 .../submarine/server/rest/ExperimentRestApi.java   |  54 ---------
 .../submarine/server/rest/RestConstants.java       |   4 +
 .../apache/submarine/server/rest/ServeRestApi.java |  98 ++++++++++++++++
 .../server-submitter/submitter-k8s/pom.xml         |   6 +
 .../server/submitter/k8s/K8sSubmitter.java         |  96 +++++++---------
 .../server/submitter/k8s/K8SJobSubmitterTest.java  |  25 +++-
 website/docs/api/serve.md                          |  78 +++++++++++++
 website/sidebars.js                                |   1 +
 24 files changed, 872 insertions(+), 196 deletions(-)

diff --git a/helm-charts/submarine/templates/rbac.yaml 
b/helm-charts/submarine/templates/rbac.yaml
index 16e4c7e..0481c61 100644
--- a/helm-charts/submarine/templates/rbac.yaml
+++ b/helm-charts/submarine/templates/rbac.yaml
@@ -53,6 +53,19 @@ rules:
   - patch
   - update
 - apiGroups:
+  - machinelearning.seldon.io
+  resources:
+  - seldondeployments
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - delete
+  - deletecollection
+  - patch
+  - update
+- apiGroups:
   - ""
   resources:
   - pods
diff --git a/pom.xml b/pom.xml
index e97bf46..333f269 100644
--- a/pom.xml
+++ b/pom.xml
@@ -145,6 +145,7 @@
   <modules>
     <module>submarine-commons</module>
     <module>submarine-client</module>
+    <module>submarine-serve</module>
     <module>submarine-server</module>
     <module>submarine-all</module>
     <module>submarine-workbench</module>
diff --git a/submarine-cloud-v2/artifacts/submarine/submarine-rbac.yaml 
b/submarine-cloud-v2/artifacts/submarine/submarine-rbac.yaml
index 2ea85a7..f568dac 100644
--- a/submarine-cloud-v2/artifacts/submarine/submarine-rbac.yaml
+++ b/submarine-cloud-v2/artifacts/submarine/submarine-rbac.yaml
@@ -55,6 +55,19 @@ rules:
   - patch
   - update
 - apiGroups:
+  - machinelearning.seldon.io
+  resources:
+  - seldondeployments
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - delete
+  - deletecollection
+  - patch
+  - update
+- apiGroups:
   - ""
   resources:
   - pods
diff --git a/submarine-server/server-submitter/submitter-k8s/pom.xml 
b/submarine-serve/pom.xml
similarity index 68%
copy from submarine-server/server-submitter/submitter-k8s/pom.xml
copy to submarine-serve/pom.xml
index 9b0870a..57ae5da 100644
--- a/submarine-server/server-submitter/submitter-k8s/pom.xml
+++ b/submarine-serve/pom.xml
@@ -17,72 +17,28 @@
   specific language governing permissions and limitations
   under the License.
   -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0";
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
   <parent>
-    <artifactId>submarine-server-submitter</artifactId>
+    <artifactId>submarine</artifactId>
     <groupId>org.apache.submarine</groupId>
     <version>0.7.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>submarine-submitter-k8s</artifactId>
+  <artifactId>submarine-serve</artifactId>
   <version>0.7.0-SNAPSHOT</version>
-  <name>Submarine: Kubernetes Submitter</name>
+  <name>Submarine: Serve</name>
 
   <dependencies>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
-      <version>${slf4j.version}</version>
     </dependency>
-
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-log4j12</artifactId>
-      <version>${slf4j.version}</version>
-    </dependency>
-
     <dependency>
       <groupId>io.kubernetes</groupId>
       <artifactId>client-java</artifactId>
-      <version>${k8s.client-java.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.submarine</groupId>
-      <artifactId>submarine-server-api</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-
-    <dependency>
-      <groupId>org.apache.submarine</groupId>
-      <artifactId>submarine-server-core</artifactId>
-      <version>${project.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>io.grpc</groupId>
-          <artifactId>grpc-protobuf</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>com.google.code.gson</groupId>
-          <artifactId>gson</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>org.tukaani</groupId>
-          <artifactId>xz</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>joda-time</groupId>
-          <artifactId>joda-time</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>mysql</groupId>
-          <artifactId>mysql-connector-java</artifactId>
-        </exclusion>
-      </exclusions>
     </dependency>
 
     <!--  Unit Tests  -->
@@ -90,6 +46,12 @@
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.submarine</groupId>
+      <artifactId>submarine-commons-utils</artifactId>
+      <version>0.7.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonDeployment.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonDeployment.java
new file mode 100644
index 0000000..483d24c
--- /dev/null
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonDeployment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.serve.seldon;
+
+import com.google.gson.annotations.SerializedName;
+import io.kubernetes.client.models.V1ObjectMeta;
+import org.apache.submarine.serve.utils.SeldonConstants;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SeldonDeployment {
+  @SerializedName("apiVersion")
+  private String apiVersion = SeldonConstants.API_VERSION;
+
+  @SerializedName("kind")
+  private String kind = SeldonConstants.KIND;
+
+  @SerializedName("metadata")
+  private V1ObjectMeta metadata;
+
+  @SerializedName("spec")
+  private SeldonDeploymentSpec spec;
+
+  // transient to avoid being serialized
+  private transient String group = SeldonConstants.GROUP;
+
+  private transient String version = SeldonConstants.VERSION;
+
+  private transient String plural = SeldonConstants.PLURAL;
+
+  public String getApiVersion() {
+    return apiVersion;
+  }
+
+  public void setApiVersion(String apiVersion) {
+    this.apiVersion = apiVersion;
+  }
+
+  public String getKind() {
+    return kind;
+  }
+
+  public void setKind(String kind) {
+    this.kind = kind;
+  }
+
+  public V1ObjectMeta getMetadata() {
+    return metadata;
+  }
+
+  public void setMetadata(V1ObjectMeta metadata) {
+    this.metadata = metadata;
+  }
+
+  public String getGroup() {
+    return group;
+  }
+
+  public void setGroup(String group) {
+    this.group = group;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  public String getPlural() {
+    return plural;
+  }
+
+  public void setPlural(String plural) {
+    this.plural = plural;
+  }
+
+  public void setSpec(SeldonDeploymentSpec seldonDeploymentSpec){
+    this.spec = seldonDeploymentSpec;
+  }
+
+  public void addPredictor(SeldonPredictor seldonPredictor) {
+    this.spec.addPredictor(seldonPredictor);
+  }
+
+  public static class SeldonDeploymentSpec {
+    public SeldonDeploymentSpec(String protocol) {
+      setProtocol(protocol);
+    }
+
+    @SerializedName("protocol")
+    private String protocol;
+    @SerializedName("predictors")
+    private List<SeldonPredictor> predictors = new ArrayList<>();
+
+    public String getProtocol() {
+      return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+      this.protocol = protocol;
+    }
+
+    public void addPredictor(SeldonPredictor seldonPredictor) {
+      predictors.add(seldonPredictor);
+    }
+  }
+}
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonGraph.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonGraph.java
new file mode 100644
index 0000000..48b7e25
--- /dev/null
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonGraph.java
@@ -0,0 +1,59 @@
+/*
+ * 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.serve.seldon;
+
+import com.google.gson.annotations.SerializedName;
+import org.apache.submarine.serve.utils.SeldonConstants;
+
+public class SeldonGraph {
+  @SerializedName("name")
+  private String name;
+  @SerializedName("implementation")
+  private String implementation;
+  @SerializedName("modelUri")
+  private String modelUri;
+  @SerializedName("storageInitializerImage")
+  private String storageInitializerImage = 
SeldonConstants.STORAGE_INITIALIZER_IMAGE;
+  @SerializedName("envSecretRefName")
+  private String envSecretRefName = SeldonConstants.ENV_SECRET_REF_NAME;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getImplementation() {
+    return implementation;
+  }
+
+  public void setImplementation(String implementation) {
+    this.implementation = implementation;
+  }
+
+  public String getModelUri() {
+    return modelUri;
+  }
+
+  public void setModelUri(String modelUri) {
+    this.modelUri = modelUri;
+  }
+}
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
new file mode 100644
index 0000000..48e5227
--- /dev/null
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.serve.seldon;
+
+import com.google.gson.annotations.SerializedName;
+
+public class SeldonPredictor {
+  @SerializedName("name")
+  private String name = "default";
+
+  @SerializedName("replicas")
+  private Integer replicas = 1;
+
+  @SerializedName("graph")
+  private SeldonGraph seldonGraph = new SeldonGraph();
+
+  public SeldonPredictor(String name, Integer replicas, SeldonGraph graph) {
+    setName(name);
+    setReplicas(replicas);
+    setSeldonGraph(graph);
+  }
+
+  public SeldonPredictor(){};
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public Integer getReplicas() {
+    return replicas;
+  }
+
+  public void setReplicas(Integer replicas) {
+    this.replicas = replicas;
+  }
+
+  public SeldonGraph getSeldonGraph() {
+    return seldonGraph;
+  }
+
+  public void setSeldonGraph(SeldonGraph seldonGraph) {
+    this.seldonGraph = seldonGraph;
+  }
+}
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/tensorflow/SeldonTFServing.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/tensorflow/SeldonTFServing.java
new file mode 100644
index 0000000..d63dbaa
--- /dev/null
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/tensorflow/SeldonTFServing.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.serve.tensorflow;
+
+import io.kubernetes.client.models.V1ObjectMeta;
+import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.serve.seldon.SeldonGraph;
+import org.apache.submarine.serve.seldon.SeldonPredictor;
+import org.apache.submarine.serve.utils.SeldonConstants;
+
+public class SeldonTFServing extends SeldonDeployment {
+  public SeldonTFServing(String name, String modelURI){
+    V1ObjectMeta v1ObjectMeta = new V1ObjectMeta();
+    v1ObjectMeta.setName(name);
+    v1ObjectMeta.setNamespace(SeldonConstants.DEFAULT_NAMESPACE);
+    setMetadata(v1ObjectMeta);
+
+    setSpec(new SeldonDeploymentSpec(SeldonConstants.SELDON_PROTOCOL));
+
+    SeldonGraph seldonGraph = new SeldonGraph();
+    seldonGraph.setName(name);
+    seldonGraph.setImplementation(SeldonConstants.TFSERVING_IMPLEMENTATION);
+    seldonGraph.setModelUri(modelURI);
+    SeldonPredictor seldonPredictor = new SeldonPredictor();
+    seldonPredictor.setSeldonGraph(seldonGraph);
+
+    addPredictor(seldonPredictor);
+  }
+}
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/utils/SeldonConstants.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/utils/SeldonConstants.java
new file mode 100644
index 0000000..bcaf0a6
--- /dev/null
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/utils/SeldonConstants.java
@@ -0,0 +1,42 @@
+/*
+ * 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.serve.utils;
+
+public class SeldonConstants {
+  public static final String API_VERSION = "machinelearning.seldon.io/v1";
+
+  public static final String KIND = "SeldonDeployment";
+
+  public static final String GROUP = "machinelearning.seldon.io";
+
+  public static final String VERSION = "v1";
+
+  public static final String PLURAL = "seldondeployments";
+
+  public static final String STORAGE_INITIALIZER_IMAGE = 
"seldonio/rclone-storage-initializer:1.10.0";
+
+  public static final String ENV_SECRET_REF_NAME = "submarine-serve-secret";
+
+  public static final String DEFAULT_NAMESPACE = "default";
+
+  public static final String SELDON_PROTOCOL = "seldon";
+
+  // TensorFlow
+  public static final String TFSERVING_IMPLEMENTATION = "TENSORFLOW_SERVER";
+}
diff --git a/submarine-serve/src/main/resources/log4j.properties 
b/submarine-serve/src/main/resources/log4j.properties
new file mode 100644
index 0000000..fdd0239
--- /dev/null
+++ b/submarine-serve/src/main/resources/log4j.properties
@@ -0,0 +1,25 @@
+#
+# 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.
+#
+
+log4j.rootLogger = info, stdout
+
+log4j.appender.stdout = org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target = System.out
+log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd 
HH:mm:ss,SSS} method:%l%n%m%n
diff --git 
a/submarine-serve/src/test/java/org/apache/submarine/serve/tensorflow/SeldonTFServingTest.java
 
b/submarine-serve/src/test/java/org/apache/submarine/serve/tensorflow/SeldonTFServingTest.java
new file mode 100644
index 0000000..8dc40db
--- /dev/null
+++ 
b/submarine-serve/src/test/java/org/apache/submarine/serve/tensorflow/SeldonTFServingTest.java
@@ -0,0 +1,22 @@
+// /*
+//  * 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.serve.tensorflow;
+
+// TODO(KUAN-HSUN LI)
diff --git 
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/Submitter.java
 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/Submitter.java
index 6185ba4..96e0f2b 100644
--- 
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/Submitter.java
+++ 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/Submitter.java
@@ -25,8 +25,8 @@ import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.api.experiment.ExperimentLog;
 import org.apache.submarine.server.api.experiment.TensorboardInfo;
 import org.apache.submarine.server.api.experiment.MlflowInfo;
-import org.apache.submarine.server.api.experiment.ServeRequest;
-import org.apache.submarine.server.api.experiment.ServeResponse;
+import org.apache.submarine.server.api.model.ServeResponse;
+import org.apache.submarine.server.api.model.ServeSpec;
 import org.apache.submarine.server.api.notebook.Notebook;
 import org.apache.submarine.server.api.spec.ExperimentSpec;
 import org.apache.submarine.server.api.spec.NotebookSpec;
@@ -126,20 +126,19 @@ public interface Submitter {
 
   /**
    * Create Serve with spec
-   * @param ServeRequest
+   * @param spec
    * @return object
    * @throws SubmarineRuntimeException running error
    */
-  ServeResponse createServe(ServeRequest spec) throws 
SubmarineRuntimeException;
+  ServeResponse createServe(ServeSpec spec) throws SubmarineRuntimeException;
 
 
   /**
    * Delete Serve with spec
-   * @param ServeRequest
-   * @return object
+   * @param spec
    * @throws SubmarineRuntimeException running error
    */
-  ServeResponse deleteServe(ServeRequest spec) throws 
SubmarineRuntimeException;
+  void deleteServe(ServeSpec spec) throws SubmarineRuntimeException;
 
   /**
    * Get tensorboard meta data
diff --git 
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeResponse.java
 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeResponse.java
new file mode 100644
index 0000000..d948abe
--- /dev/null
+++ 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeResponse.java
@@ -0,0 +1,31 @@
+/*
+ * 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.model;
+
+public class ServeResponse {
+  private String url;
+
+  public String getUrl() {
+    return url;
+  }
+
+  public void setUrl(String url) {
+    this.url = url;
+  }
+}
diff --git 
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeSpec.java
 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeSpec.java
new file mode 100644
index 0000000..0a0f8fe
--- /dev/null
+++ 
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/model/ServeSpec.java
@@ -0,0 +1,58 @@
+/*
+ * 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.model;
+
+public class ServeSpec {
+  private String modelName;
+  private Integer modelVersion;
+  private String modelType;
+  private String modelURI;
+
+  public String getModelName() {
+    return modelName;
+  }
+
+  public void setModelName(String modelName) {
+    this.modelName = modelName;
+  }
+
+  public Integer getModelVersion() {
+    return modelVersion;
+  }
+
+  public void setModelVersion(Integer modelVersion) {
+    this.modelVersion = modelVersion;
+  }
+
+  public String getModelType() {
+    return modelType;
+  }
+
+  public void setModelType(String modelType) {
+    this.modelType = modelType;
+  }
+
+  public String getModelURI() {
+    return modelURI;
+  }
+
+  public void setModelURI(String modelURI) {
+    this.modelURI = modelURI;
+  }
+}
diff --git 
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experiment/ExperimentManager.java
 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experiment/ExperimentManager.java
index f0776d1..e0764d1 100644
--- 
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/experiment/ExperimentManager.java
+++ 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/experiment/ExperimentManager.java
@@ -41,8 +41,6 @@ import org.apache.submarine.server.api.Submitter;
 import org.apache.submarine.server.api.experiment.ExperimentLog;
 import org.apache.submarine.server.api.experiment.TensorboardInfo;
 import org.apache.submarine.server.api.experiment.MlflowInfo;
-import org.apache.submarine.server.api.experiment.ServeRequest;
-import org.apache.submarine.server.api.experiment.ServeResponse;
 import org.apache.submarine.server.api.spec.ExperimentSpec;
 import org.apache.submarine.server.experiment.database.entity.ExperimentEntity;
 import 
org.apache.submarine.server.experiment.database.service.ExperimentService;
@@ -346,32 +344,6 @@ public class ExperimentManager {
     return submitter.getMlflowInfo();
   }
 
-  /**
-   * Create serve.
-   *
-   * @param spec spec
-   * @return object
-   * @throws SubmarineRuntimeException the service error
-   */
-  public ServeResponse createServe(ServeRequest spec) throws 
SubmarineRuntimeException {
-    // TODO(byronhsu): use mlflow api to make sure the model exists. 
Otherwise, raise exception.
-    ServeResponse serve = submitter.createServe(spec);
-    return serve;
-  }
-
-  /**
-   * Delete serve.
-   *
-   * @param spec spec
-   * @return object
-   * @throws SubmarineRuntimeException the service error
-   */
-  public ServeResponse deleteServe(ServeRequest spec) throws 
SubmarineRuntimeException {
-    ServeResponse serve = submitter.deleteServe(spec);
-    return serve;
-  }
-
-
   private void checkSpec(ExperimentSpec spec) throws SubmarineRuntimeException 
{
     if (spec == null) {
       throw new SubmarineRuntimeException(Status.OK.getStatusCode(), "Invalid 
experiment spec.");
diff --git 
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/model/ModelManager.java
 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/model/ModelManager.java
new file mode 100644
index 0000000..49ceb6e
--- /dev/null
+++ 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/model/ModelManager.java
@@ -0,0 +1,110 @@
+/*
+ * 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.model;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.core.Response;
+
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.server.SubmitterManager;
+import org.apache.submarine.server.api.Submitter;
+import org.apache.submarine.server.api.model.ServeResponse;
+import org.apache.submarine.server.api.model.ServeSpec;
+import org.apache.submarine.server.model.database.service.ModelVersionService;
+
+
+
+public class ModelManager {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ModelManager.class);
+
+  private static volatile ModelManager manager;
+
+  private final Submitter submitter;
+
+  private final ModelVersionService modelVersionService;
+
+  private ModelManager(Submitter submitter, ModelVersionService 
modelVersionService) {
+    this.submitter = submitter;
+    this.modelVersionService = modelVersionService;
+  }
+
+  /**
+   * Get the singleton instance.
+   *
+   * @return object
+   */
+  public static ModelManager getInstance() {
+    if (manager == null) {
+      synchronized (ModelManager.class) {
+        if (manager == null) {
+          manager = new ModelManager(SubmitterManager.loadSubmitter(), new 
ModelVersionService());
+        }
+      }
+    }
+    return manager;
+  }
+
+  /**
+   * Create a model serve.
+   */
+  public ServeResponse createServe(ServeSpec spec) throws 
SubmarineRuntimeException {
+    checkServeSpec(spec);
+    String modelURI = modelVersionService.select(spec.getModelName(), 
spec.getModelVersion()).getSource();
+    spec.setModelURI(modelURI);
+
+    ServeResponse serveResponse = submitter.createServe(spec);
+    return serveResponse;
+  }
+
+  /**
+   * Delete a model serve.
+   */
+  public void deleteServe(ServeSpec spec) throws SubmarineRuntimeException {
+    checkServeSpec(spec);
+    String modelURI = modelVersionService.select(spec.getModelName(), 
spec.getModelVersion()).getSource();
+    spec.setModelURI(modelURI);
+
+    submitter.deleteServe(spec);
+  }
+
+  private void checkServeSpec(ServeSpec spec) throws SubmarineRuntimeException 
{
+    if (spec == null) {
+      throw new SubmarineRuntimeException(Response.Status.OK.getStatusCode(),
+              "Invalid. Serve Spec object is null.");
+    } else {
+      if (spec.getModelName() == null) {
+        throw new SubmarineRuntimeException(Response.Status.OK.getStatusCode(),
+                "Invalid. Model name in Serve Soec is null.");
+      }
+      Integer modelVersion = spec.getModelVersion();
+      if (modelVersion == null || modelVersion <= 0) {
+        throw new SubmarineRuntimeException(Response.Status.OK.getStatusCode(),
+                "Invalid. Model version must be positive, but get " + 
modelVersion);
+      }
+      String modelType = spec.getModelType();
+      if (!modelType.equals("tensorflow") && !modelType.equals("pytorch")) {
+        throw new SubmarineRuntimeException(Response.Status.OK.getStatusCode(),
+                "Invalid. Serve Type can only be tensorflow or pytorch, but 
get " + modelType);
+      }
+    }
+  }
+}
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 4a1efa7..76331a2 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
@@ -43,8 +43,6 @@ import 
org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
 import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.api.experiment.TensorboardInfo;
 import org.apache.submarine.server.api.experiment.MlflowInfo;
-import org.apache.submarine.server.api.experiment.ServeRequest;
-import org.apache.submarine.server.api.experiment.ServeResponse;
 import org.apache.submarine.server.experiment.ExperimentManager;
 import 
org.apache.submarine.server.experimenttemplate.ExperimentTemplateManager;
 import org.apache.submarine.server.api.experiment.ExperimentLog;
@@ -300,58 +298,6 @@ public class ExperimentRestApi {
     }
   }
 
-  /**
-   * Returns the contents of {@link Serve} that submitted by user.
-   *
-   * @param spec spec
-   * @return the contents of serve
-   */
-
-  @POST
-  @Path("/serve")
-  @Consumes({RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON})
-  @Operation(summary = "Create an serve",
-      tags = {"serve"},
-      responses = {
-          @ApiResponse(description = "successful operation", content = 
@Content(
-              schema = @Schema(implementation = JsonResponse.class)))})
-  public Response createServe(ServeRequest spec) {
-    try {
-      ServeResponse serve = experimentManager.createServe(spec);
-      return new 
JsonResponse.Builder<ServeResponse>(Response.Status.OK).success(true)
-          .result(serve).build();
-    } catch (SubmarineRuntimeException e) {
-      return parseExperimentServiceException(e);
-    }
-  }
-
-  /**
-   * Delete {@link Serve} that submitted by user.
-   *
-   * @param spec spec
-   * @return the contents of serve
-   */
-
-  @DELETE
-  @Path("/serve")
-  @Operation(summary = "Delete a serve",
-      tags = {"serve"},
-      responses = {
-          @ApiResponse(description = "successful operation", content = 
@Content(
-              schema = @Schema(implementation = JsonResponse.class)))})
-  public Response deleteServe(@QueryParam("modelName") String modelName,
-      @QueryParam("modelVersion") String modelVersion, 
@QueryParam("namespace") String namespace) {
-    try {
-      ServeRequest spec = new ServeRequest()
-          
.modelName(modelName).modelVersion(modelVersion).namespace(namespace);
-      ServeResponse serve = experimentManager.deleteServe(spec);
-      return new 
JsonResponse.Builder<ServeResponse>(Response.Status.OK).success(true)
-          .result(serve).build();
-    } catch (SubmarineRuntimeException e) {
-      return parseExperimentServiceException(e);
-    }
-  }
-
   private Response parseExperimentServiceException(SubmarineRuntimeException 
e) {
     return new JsonResponse.Builder<String>(e.getCode())
       .message(e.getMessage().equals("Conflict") ? "Duplicated experiment 
name" : 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 e6555fa..3137087 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
@@ -89,4 +89,8 @@ public class RestConstants {
   public static final String MODEL_VERSION_NAME = "name";
   public static final String MODEL_VERSION_VERSION = "version";
 
+  /**
+   * Serve.
+   */
+  public static final String SERVE = "serve";
 }
diff --git 
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ServeRestApi.java
 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ServeRestApi.java
new file mode 100644
index 0000000..65f9d72
--- /dev/null
+++ 
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/ServeRestApi.java
@@ -0,0 +1,98 @@
+/*
+ * 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 javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+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;
+
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.server.api.model.ServeResponse;
+import org.apache.submarine.server.api.model.ServeSpec;
+import org.apache.submarine.server.model.ModelManager;
+import org.apache.submarine.server.response.JsonResponse;
+
+
+
+@Path(RestConstants.V1 + "/" + RestConstants.SERVE)
+@Produces({MediaType.APPLICATION_JSON + "; " + RestConstants.CHARSET_UTF8})
+public class ServeRestApi {
+
+  private final ModelManager modelManager = ModelManager.getInstance();
+
+  /**
+   * Return the Pong message for test the connectivity.
+   * @return Pong message
+   */
+  @GET
+  @Path(RestConstants.PING)
+  @Consumes(MediaType.APPLICATION_JSON)
+  @Operation(summary = "Ping submarine server",
+             tags = {"serve"},
+             description = "Return the Pong message for test the connectivity",
+             responses = {@ApiResponse(responseCode = "200", description = 
"successful operation",
+             content = @Content(schema = @Schema(implementation = 
String.class)))})
+  public Response ping() {
+    return new JsonResponse.Builder<String>(Response.Status.OK)
+            .success(true).result("Pong").build();
+  }
+
+  @POST
+  @Consumes({ RestConstants.MEDIA_TYPE_YAML, MediaType.APPLICATION_JSON })
+  @Operation(summary = "Create a serve instance", tags = { "serve" }, 
responses = {
+          @ApiResponse(description = "successful operation",
+                  content = @Content(schema = @Schema(implementation = 
JsonResponse.class)))})
+  public Response createServe(ServeSpec spec) {
+    try {
+      ServeResponse serveResponse = modelManager.createServe(spec);
+      return new 
JsonResponse.Builder<ServeResponse>(Response.Status.OK).success(true)
+              .message("Create a serve 
instance").result(serveResponse).build();
+    } catch (SubmarineRuntimeException e) {
+      return parseModelVersionServiceException(e);
+    }
+  }
+
+  @DELETE
+  @Operation(summary = "Delete the serve instance.", tags = { "serve" }, 
responses = {
+          @ApiResponse(description = "successful operation",
+                  content = @Content(schema = @Schema(implementation = 
JsonResponse.class))),
+          @ApiResponse(responseCode = "404", description = "Serve not 
found.")})
+  public Response deleteServe(ServeSpec spec) {
+    try {
+      modelManager.deleteServe(spec);
+      return new JsonResponse.Builder<String>(Response.Status.OK).success(true)
+              .message("Delete the model serve instance").build();
+    } catch (SubmarineRuntimeException e) {
+      return parseModelVersionServiceException(e);
+    }
+  }
+
+  private Response parseModelVersionServiceException(SubmarineRuntimeException 
e) {
+    return new 
JsonResponse.Builder<String>(e.getCode()).message(e.getMessage()).build();
+  }
+}
diff --git a/submarine-server/server-submitter/submitter-k8s/pom.xml 
b/submarine-server/server-submitter/submitter-k8s/pom.xml
index 9b0870a..a0e7e4d 100644
--- a/submarine-server/server-submitter/submitter-k8s/pom.xml
+++ b/submarine-server/server-submitter/submitter-k8s/pom.xml
@@ -90,6 +90,12 @@
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
     </dependency>
+      <dependency>
+          <groupId>org.apache.submarine</groupId>
+          <artifactId>submarine-serve</artifactId>
+          <version>0.7.0-SNAPSHOT</version>
+          <scope>compile</scope>
+      </dependency>
   </dependencies>
 
   <build>
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
index cac7398..507dde9 100644
--- 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/K8sSubmitter.java
@@ -49,7 +49,6 @@ import io.kubernetes.client.models.V1ObjectMeta;
 import io.kubernetes.client.models.V1PersistentVolumeClaim;
 import io.kubernetes.client.models.V1Pod;
 import io.kubernetes.client.models.V1PodList;
-import io.kubernetes.client.models.V1Service;
 import io.kubernetes.client.models.V1Status;
 import io.kubernetes.client.util.Watch;
 import io.kubernetes.client.util.ClientBuilder;
@@ -57,14 +56,16 @@ import io.kubernetes.client.util.KubeConfig;
 
 import org.apache.submarine.commons.utils.SubmarineConfiguration;
 import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.serve.tensorflow.SeldonTFServing;
 import org.apache.submarine.server.api.Submitter;
 import org.apache.submarine.server.api.exception.InvalidSpecException;
 import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.api.experiment.ExperimentLog;
 import org.apache.submarine.server.api.experiment.TensorboardInfo;
 import org.apache.submarine.server.api.experiment.MlflowInfo;
-import org.apache.submarine.server.api.experiment.ServeRequest;
-import org.apache.submarine.server.api.experiment.ServeResponse;
+import org.apache.submarine.server.api.model.ServeResponse;
+import org.apache.submarine.server.api.model.ServeSpec;
 import org.apache.submarine.server.api.notebook.Notebook;
 import org.apache.submarine.server.api.spec.ExperimentMeta;
 import org.apache.submarine.server.api.spec.ExperimentSpec;
@@ -74,12 +75,10 @@ import 
org.apache.submarine.server.submitter.k8s.model.NotebookCR;
 import 
org.apache.submarine.server.submitter.k8s.model.ingressroute.IngressRoute;
 import 
org.apache.submarine.server.submitter.k8s.model.ingressroute.IngressRouteSpec;
 import org.apache.submarine.server.submitter.k8s.model.ingressroute.SpecRoute;
-import org.apache.submarine.server.submitter.k8s.model.middlewares.Middlewares;
 import org.apache.submarine.server.submitter.k8s.model.pytorchjob.PyTorchJob;
 import org.apache.submarine.server.submitter.k8s.model.tfjob.TFJob;
 import org.apache.submarine.server.submitter.k8s.parser.ExperimentSpecParser;
 import org.apache.submarine.server.submitter.k8s.parser.NotebookSpecParser;
-import org.apache.submarine.server.submitter.k8s.parser.ServeSpecParser;
 import org.apache.submarine.server.submitter.k8s.parser.VolumeSpecParser;
 import org.apache.submarine.server.submitter.k8s.util.MLJobConverter;
 import org.apache.submarine.server.submitter.k8s.util.NotebookUtils;
@@ -117,6 +116,7 @@ public class K8sSubmitter implements Submitter {
   public void initialize(SubmarineConfiguration conf) {
     try {
       String path = System.getenv(KUBECONFIG_ENV);
+      //      path = System.getProperty("user.home") + "/.kube/config"; 
//TODO(tmp)
       KubeConfig config = KubeConfig.loadKubeConfig(new FileReader(path));
       client = ClientBuilder.kubeconfig(config).build();
     } catch (Exception e) {
@@ -518,67 +518,39 @@ public class K8sSubmitter implements Submitter {
   }
 
   @Override
-  public ServeResponse createServe(ServeRequest spec)
+  public ServeResponse createServe(ServeSpec spec)
       throws SubmarineRuntimeException {
-    String modelName = spec.getModelName();
-    String modelVersion = spec.getModelVersion();
-    String namespace = spec.getNamespace();
-
-    ServeSpecParser parser = new ServeSpecParser(modelName, modelVersion, 
namespace);
-    V1Deployment deployment = parser.getDeployment();
-    V1Service svc = parser.getService();
-    IngressRoute ingressRoute = parser.getIngressRoute();
-    Middlewares middleware = parser.getMiddlewares();
-    ServeResponse serveInfo = new ServeResponse().url(parser.getRoutePath());
+    SeldonDeployment seldonDeployment = parseServeSpec(spec);
 
     try {
-      appsV1Api.createNamespacedDeployment(namespace, deployment, "true", 
null, null);
-      coreApi.createNamespacedService(namespace, svc, "true", null, null);
-
-      api.createNamespacedCustomObject(
-          middleware.getGroup(), middleware.getVersion(),
-          middleware.getMetadata().getNamespace(),
-          middleware.getPlural(), middleware, "true");
-
-      api.createNamespacedCustomObject(
-          ingressRoute.getGroup(), ingressRoute.getVersion(),
-          ingressRoute.getMetadata().getNamespace(),
-          ingressRoute.getPlural(), ingressRoute, "true");
-      return serveInfo;
+      api.createNamespacedCustomObject(seldonDeployment.getGroup(),
+              seldonDeployment.getVersion(),
+              "default",
+              seldonDeployment.getPlural(),
+              seldonDeployment,
+              "true");
     } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
       throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
     }
+    return new ServeResponse();
   }
 
   @Override
-  public ServeResponse deleteServe(ServeRequest spec)
+  public void deleteServe(ServeSpec spec)
       throws SubmarineRuntimeException {
-    String modelName = spec.getModelName();
-    String modelVersion = spec.getModelVersion();
-    String namespace = spec.getNamespace();
-
-    ServeSpecParser parser = new ServeSpecParser(modelName, modelVersion, 
namespace);
-    IngressRoute ingressRoute = parser.getIngressRoute();
-    Middlewares middleware = parser.getMiddlewares();
-    ServeResponse serveInfo = new ServeResponse().url(parser.getRoutePath());
+    SeldonDeployment seldonDeployment = parseServeSpec(spec);
 
     try {
-      appsV1Api.deleteNamespacedDeployment(parser.getGeneralName(), namespace, 
"true",
-          null, null, null, null, null);
-      coreApi.deleteNamespacedService(parser.getSvcName(), namespace, "true",
-          null, null, null, null, null);
-      api.deleteNamespacedCustomObject(
-          middleware.getGroup(), middleware.getVersion(),
-          middleware.getMetadata().getNamespace(), middleware.getPlural(), 
parser.getMiddlewareName(),
-          new 
V1DeleteOptionsBuilder().withApiVersion(middleware.getApiVersion()).build(),
-          null, null, null);
-      api.deleteNamespacedCustomObject(
-          ingressRoute.getGroup(), ingressRoute.getVersion(),
-          ingressRoute.getMetadata().getNamespace(), ingressRoute.getPlural(), 
parser.getRouteName(),
-          new 
V1DeleteOptionsBuilder().withApiVersion(ingressRoute.getApiVersion()).build(),
-          null, null, null);
-      return serveInfo;
+      api.deleteNamespacedCustomObject(seldonDeployment.getGroup(),
+              seldonDeployment.getVersion(),
+              "default",
+              seldonDeployment.getPlural(),
+              seldonDeployment.getMetadata().getName(),
+              new 
V1DeleteOptionsBuilder().withApiVersion(seldonDeployment.getApiVersion()).build(),
+              null, null, null);
     } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
       throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
     }
   }
@@ -711,7 +683,6 @@ public class K8sSubmitter implements Submitter {
   }
 
   private String getJobLabelSelector(ExperimentSpec experimentSpec) {
-    // TODO(JohnTing): SELECTOR_KEY should be obtained from individual models 
in MLJOB
     if (experimentSpec.getMeta().getFramework()
         
.equalsIgnoreCase(ExperimentMeta.SupportedMLFramework.TENSORFLOW.getName())) {
       return TF_JOB_SELECTOR_KEY + experimentSpec.getMeta().getExperimentId();
@@ -776,6 +747,23 @@ public class K8sSubmitter implements Submitter {
     return spec;
   }
 
+  private SeldonDeployment parseServeSpec(ServeSpec spec) throws 
SubmarineRuntimeException {
+    String modelName = spec.getModelName();
+    String modelType = spec.getModelType();
+    String modelURI = spec.getModelURI();
+
+    SeldonDeployment seldonDeployment;
+    if (modelType.equals("tensorflow")){
+      seldonDeployment = new SeldonTFServing(modelName, modelURI);
+    } else if (modelType.equals("pytorch")){
+      // TODO(KUAN-HSUN LI): create pytorch serve
+      throw new SubmarineRuntimeException("Given serve type: " + modelType + " 
is not supported.");
+    } else {
+      throw new SubmarineRuntimeException("Given serve type: " + modelType + " 
is not supported.");
+    }
+    return seldonDeployment;
+  }
+
   private void rollbackCreationPVC(String pvcName, String namespace) {
     try {
       deletePersistentVolumeClaim(pvcName, namespace);
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/K8SJobSubmitterTest.java
 
b/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/K8SJobSubmitterTest.java
index 5be013c..64678fc 100644
--- 
a/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/K8SJobSubmitterTest.java
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/K8SJobSubmitterTest.java
@@ -27,7 +27,7 @@ import 
org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
 import org.apache.submarine.server.api.experiment.Experiment;
 import org.apache.submarine.server.api.experiment.TensorboardInfo;
 import org.apache.submarine.server.api.experiment.MlflowInfo;
-import org.apache.submarine.server.api.experiment.ServeRequest;
+import org.apache.submarine.server.api.model.ServeSpec;
 import org.apache.submarine.server.api.spec.ExperimentSpec;
 import org.junit.Assert;
 import org.junit.Before;
@@ -61,12 +61,25 @@ public class K8SJobSubmitterTest extends SpecBuilder {
     submitter.initialize(null);
   }
 
-  @Ignore // TODO(byronhsu): make sure saving a model before create serve
+  @Ignore // TODO(KUAN-HSUN LI): make sure saving a model before create serve
   @Test
-  public void tmpTest() {
-    ServeRequest request = new ServeRequest()
-          .modelName("simple-nn-model").modelVersion("1").namespace("default");
-    submitter.createServe(request);
+  public void testCreateTensorFlowServe() {
+    ServeSpec spec = new ServeSpec();
+    spec.setModelName("simple");
+    spec.setModelVersion(1);
+    spec.setModelType("tensorflow");
+    spec.setModelURI("s3://submarine/simple");
+    submitter.createServe(spec);
+  }
+
+  @Ignore // TODO(KUAN-HSUN LI): make sure saving a model before create serve
+  @Test
+  public void testDeleteTensorFlowServe() {
+    ServeSpec spec = new ServeSpec();
+    spec.setModelName("simple");
+    spec.setModelVersion(1);
+    spec.setModelType("tensorflow");
+    submitter.deleteServe(spec);
   }
 
   @Test
diff --git a/website/docs/api/serve.md b/website/docs/api/serve.md
new file mode 100644
index 0000000..b4b179f
--- /dev/null
+++ b/website/docs/api/serve.md
@@ -0,0 +1,78 @@
+---
+title: Serve REST API
+---
+
+<!--
+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.
+-->
+
+> Note: The Serv API is in the alpha stage which is subjected to incompatible 
changes in future releases.
+
+## Create a TensorFlow model serve
+`POST /api/v1/serve`
+
+**Example Request**
+> Make sure there is a model named `simple` with version `1` in the database.
+
+```sh
+curl -X POST -H "Content-Type: application/json" -d '
+{
+  "modelName": "simple", 
+  "modelVersion":1, 
+  "modelType":"tensorflow"
+}
+' http://127.0.0.1:32080/api/v1/serve
+```
+
+**Example Response:**
+```json
+{
+  "status":"OK",
+  "code":200,
+  "success":true,
+  "message":"Create a serve instance",
+  "result":{"url":null},
+  "attributes":{}
+}
+```
+
+## Delete the TensorFlow model serve
+`DELETE /api/v1/serve`
+
+**Example Request**
+```sh
+curl -X DELETE -H "Content-Type: application/json" -d '
+{
+  "modelName": "simple", 
+  "modelVersion":1, 
+  "modelType":"tensorflow"
+}
+' http://127.0.0.1:32080/api/v1/serve
+```
+
+**Example Response:**
+```json
+{
+  "status":"OK",
+  "code":200,
+  "success":true,
+  "message":"Delete the model serve instance",
+  "result":null,
+  "attributes":{}
+}
+```
diff --git a/website/sidebars.js b/website/sidebars.js
index 6679baa..794742d 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -102,5 +102,6 @@ module.exports = {
         "api/experiment",
         "api/experiment-template",
         "api/notebook",
+        "api/serve",
     ],
 };

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to