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 b2c7e81f SUBMARINE-1315. Fix model can not serve error (#992)
b2c7e81f is described below

commit b2c7e81f0123b736fc16cc498886c6062e68a4fb
Author: Thinking Chen <cdmikec...@hotmail.com>
AuthorDate: Tue Sep 13 18:51:22 2022 +0800

    SUBMARINE-1315. Fix model can not serve error (#992)
    
    ### What is this PR for?
    The model serve now registers the wrong namespace for resources, and taking 
the model name directly as a resource name can lead to problems with illegal 
characters.
    
![image](https://user-images.githubusercontent.com/12069428/187067306-98067ede-1c8a-4c0a-b356-96607519d40e.png)
    
    
    ### What type of PR is it?
    Bug Fix
    
    ### Todos
    * [x] - Model serve codes refactoring.
    * [x] - Replace default to submarine-server namespace.
    * [x] - Add a primary key id for `registered_model`(model_id) and 
`model_version` (model_version_id)
    * [x] - Replace model service name to `submarine-model-${id}-@{modelId}` in 
k8s resource metadata name.
    * [x] - Replace SeldonGraph container name to `version-${modelVersion}`.
    * [x] - Replace ingress routing to 
`/seldon/${namespace}/${modelVersionId}/${modelVersion}/` and add owner 
reference for cascaded deletion of operator.
    * [x] - Add save_model method and serve predictions example in example 
quick-start .
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/projects/SUBMARINE/issues/SUBMARINE-1315
    
    ### How should this be tested?
    Add a simple test case to test k8s resource.
    
    ### Screenshots (if appropriate)
    There is still a legacy of problems. Now that seldon automatically creates 
the serve services (http and rpc) using istio, the `VirtualService` we create 
for our own serve will be duplicated, and the url provided by the seldon 
returned by calling swagger with our own `VirtualService` (like 
http://localhost:32080/seldon/submarine-user-test/1/1/api/v1.0/doc/) is still 
seldon http `VirtualService` url.
    
    The first was created by us and the following two by seldon. Both are 
currently accessible.
    
![image](https://user-images.githubusercontent.com/12069428/188297152-09022bfa-ab4b-412e-b694-3a928d8b1a50.png)
    
    Swagger UI
    
![image](https://user-images.githubusercontent.com/12069428/188297072-a9904689-0e05-43f6-8624-a994a44799de.png)
    
    
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? Yes
    * Does this need new documentation? No
---
 dev-support/database/submarine-model.sql           |  26 +++--
 .../examples/quickstart/serve_predictions.py       | 112 ++++++++++++++++++
 dev-support/examples/quickstart/train.py           |   2 +
 submarine-serve/pom.xml                            |  12 ++
 .../serve/istio/IstioHTTPDestination.java          |  45 ++++++++
 .../serve/istio/IstioHTTPMatchRequest.java         |  27 +++++
 .../submarine/serve/istio/IstioHTTPRoute.java      |  39 ++++++-
 .../submarine/serve/istio/IstioVirtualService.java |   9 +-
 .../serve/istio/IstioVirtualServiceList.java       |  21 +++-
 .../serve/istio/IstioVirtualServiceSpec.java       |  34 +++++-
 ...donPredictor.java => PredictorAnnotations.java} |  63 +++++------
 .../submarine/serve/seldon/SeldonDeployment.java   | 125 ++++++++++++++++-----
 ...donPredictor.java => SeldonDeploymentSpec.java} |  47 ++++----
 .../apache/submarine/serve/seldon/SeldonGraph.java |  19 ++++
 .../submarine/serve/seldon/SeldonPredictor.java    |  15 +++
 .../{ => seldon}/pytorch/SeldonPytorchServing.java |  20 ++--
 .../{ => seldon}/tensorflow/SeldonTFServing.java   |  20 ++--
 .../submarine/serve/utils/SeldonConstants.java     |   2 -
 .../submarine/server/api/model/ServeSpec.java      |  10 ++
 submarine-server/server-core/pom.xml               |  10 ++
 .../model/entities/ModelVersionEntity.java         |  14 ++-
 .../model/entities/RegisteredModelEntity.java      |  13 ++-
 .../database/mappers/ModelVersionMapper.xml        |   6 +-
 .../database/mappers/RegisteredModelMapper.xml     |   6 +-
 .../server-submitter/submitter-k8s/pom.xml         |   2 +-
 .../server/submitter/k8s/K8sSubmitter.java         |  75 +++----------
 .../k8s/model/istio/IstioVirtualService.java       |  10 +-
 .../k8s/model/seldon/SeldonDeploymentFactory.java  |  55 +++++++++
 .../seldon/SeldonDeploymentPytorchServing.java     | 107 ++++++++++++++++++
 .../model/seldon/SeldonDeploymentTFServing.java    | 106 +++++++++++++++++
 .../submitter/k8s/model/seldon/SeldonResource.java |  27 ++---
 .../server/submitter/k8s/util/YamlUtils.java       |  14 +++
 .../k8s/seldon/SeldonDeploymentResourceTest.java   |  86 ++++++++++++++
 .../workbench-web/src/app/interfaces/model-info.ts |   4 +-
 .../src/app/interfaces/model-serve.ts              |   4 +-
 .../model/model-info/model-info.component.html     |   8 +-
 .../model/model-info/model-info.component.ts       |  20 ++--
 .../src/app/services/model-serve.service.ts        |   8 +-
 38 files changed, 994 insertions(+), 229 deletions(-)

diff --git a/dev-support/database/submarine-model.sql 
b/dev-support/database/submarine-model.sql
index 0726323a..b3b718c0 100644
--- a/dev-support/database/submarine-model.sql
+++ b/dev-support/database/submarine-model.sql
@@ -11,17 +11,28 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+-- Since the associated tables have primary and foreign key constraints,
+-- we need to delete them all before creating them
+DROP TABLE IF EXISTS `param`;
+DROP TABLE IF EXISTS `metric`;
+DROP TABLE IF EXISTS `model_version_tag`;
+DROP TABLE IF EXISTS `model_version`;
+DROP TABLE IF EXISTS `registered_model_tag`;
 DROP TABLE IF EXISTS `registered_model`;
+
+-- Add model_id primary key
+-- It is currently designed to solve some of the problems with the model serve,
+-- and does not solve the same name problem for multiple users at the moment
 CREATE TABLE `registered_model` (
+    `model_id` BIGINT AUTO_INCREMENT,
        `name` VARCHAR(256) NOT NULL,
        `creation_time` DATETIME(3) COMMENT 'Millisecond precision',
        `last_updated_time` DATETIME(3) COMMENT 'Millisecond precision',
        `description` VARCHAR(5000),
-       CONSTRAINT `registered_model_pk` PRIMARY KEY (`name`),
+       CONSTRAINT `registered_model_pk` PRIMARY KEY (`model_id`),
        UNIQUE (`name`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `registered_model_tag`;
 CREATE TABLE `registered_model_tag` (
        `name` VARCHAR(256) NOT NULL,
        `tag` VARCHAR(256) NOT NULL,
@@ -29,8 +40,11 @@ CREATE TABLE `registered_model_tag` (
        FOREIGN KEY(`name`) REFERENCES `registered_model` (`name`) ON UPDATE 
CASCADE ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `model_version`;
+-- Add model_version_id primary key
+-- It is currently designed to solve some of the problems with the model serve,
+-- and does not solve the same name problem for multiple users at the moment
 CREATE TABLE `model_version` (
+    `model_version_id` BIGINT AUTO_INCREMENT,
        `name` VARCHAR(256) NOT NULL COMMENT 'Name of model',
        `version` INTEGER NOT NULL,
        `id` VARCHAR(64) NOT NULL COMMENT 'Id of the model',
@@ -42,12 +56,12 @@ CREATE TABLE `model_version` (
        `last_updated_time` DATETIME(3) COMMENT 'Millisecond precision',
        `dataset` VARCHAR(256) COMMENT 'Which dataset is used',
        `description` VARCHAR(5000),
-       CONSTRAINT `model_version_pk` PRIMARY KEY (`name`, `version`),
+       CONSTRAINT `model_version_pk` PRIMARY KEY (`model_version_id`),
+    UNIQUE (`name`, `version`),
        UNIQUE (`name`, `id`),
        FOREIGN KEY(`name`) REFERENCES `registered_model` (`name`) ON UPDATE 
CASCADE ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `model_version_tag`;
 CREATE TABLE `model_version_tag` (
        `name` VARCHAR(256) NOT NULL COMMENT 'Name of model',
        `version` INTEGER NOT NULL,
@@ -56,7 +70,6 @@ CREATE TABLE `model_version_tag` (
        FOREIGN KEY(`name`, `version`) REFERENCES `model_version` (`name`, 
`version`) ON UPDATE CASCADE ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `metric`;
 CREATE TABLE `metric` (
        `id` VARCHAR(64) NOT NULL COMMENT 'Id of the Experiment',
        `key` VARCHAR(190) NOT NULL COMMENT 'Metric key: `String` (limit 190 
characters). Part of *Primary Key* for ``metric`` table.',
@@ -70,7 +83,6 @@ CREATE TABLE `metric` (
        FOREIGN KEY(`id`) REFERENCES `experiment` (`id`) ON UPDATE CASCADE ON 
DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `param`;
 CREATE TABLE `param` (
        `id` VARCHAR(64) NOT NULL COMMENT 'Id of the Experiment',
        `key` VARCHAR(190) NOT NULL COMMENT '`String` (limit 190 characters). 
Part of *Primary Key* for ``param`` table.',
diff --git a/dev-support/examples/quickstart/serve_predictions.py 
b/dev-support/examples/quickstart/serve_predictions.py
new file mode 100644
index 00000000..e2e90400
--- /dev/null
+++ b/dev-support/examples/quickstart/serve_predictions.py
@@ -0,0 +1,112 @@
+# Copyright 2020 The Kubeflow Authors. All Rights Reserved.
+#
+# Licensed 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.
+# 
==============================================================================
+"""
+The code is mainly referenced from:
+https://docs.seldon.io/projects/seldon-core/en/latest/examples/tfserving_mnist.html
+https://www.tensorflow.org/tfx/tutorials/serving/rest_simple
+And the parameters of the predictions call have been modified.
+"""
+
+import numpy as np
+import requests
+import tensorflow as tf
+import tensorflow_datasets as tfds
+from matplotlib import pyplot as plt
+from packaging.version import Version
+
+
+def show_image(image):
+    """
+    show the image
+    """
+    two_d = (np.reshape(image, (28, 28)) * 255).astype(np.uint8)
+    plt.imshow(two_d, cmap=plt.cm.gray_r, interpolation="nearest")
+    plt.show()
+
+
+def rest_request_ambassador(endpoint="localhost:32080", prefix="/", arr=None):
+    """
+    request ambassador with rest
+    """
+    from tensorflow.python.ops.numpy_ops import np_config
+
+    np_config.enable_numpy_behavior()
+    """
+    you can use `saved_model_cli` command to show model inputs and outputs:
+    saved_model_cli show --dir ${model_path} --all
+
+    MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
+
+    signature_def['__saved_model_init_op']:
+      The given SavedModel SignatureDef contains the following input(s):
+      The given SavedModel SignatureDef contains the following output(s):
+        outputs['__saved_model_init_op'] tensor_info:
+            dtype: DT_INVALID
+            shape: unknown_rank
+            name: NoOp
+      Method name is:
+
+    signature_def['serving_default']:
+      The given SavedModel SignatureDef contains the following input(s):
+        inputs['conv2d_input'] tensor_info:
+            dtype: DT_FLOAT
+            shape: (-1, 28, 28, 1)
+            name: serving_default_conv2d_input:0
+      The given SavedModel SignatureDef contains the following output(s):
+        outputs['dense_1'] tensor_info:
+            dtype: DT_FLOAT
+            shape: (-1, 10)
+            name: StatefulPartitionedCall:0
+      Method name is: tensorflow/serving/predict
+    """
+    payload = {
+        "data": {
+            "names": ["image_predictions"],
+            "tensor": {"shape": [-1, 28, 28, 1], "values": arr.tolist()},
+        }
+    }
+    response = requests.post(
+        # you can also find a swagger ui in 
http://endpoint/prefix/api/v0.1/doc/
+        "http://"; + endpoint + prefix + "api/v0.1/predictions",
+        json=payload,
+    )
+    print(response.text)
+    # get the prediction
+    print(f'TThe prediction is 
{np.argmax(response.json()["data"]["tensor"]["values"])}.')
+
+
+# download datasets
+if Version(tfds.__version__) > Version("3.1.0"):
+    tfds.core.utils.gcs_utils._is_gcs_disabled = True
+datasets, _ = tfds.load(name="mnist", with_info=True, as_supervised=True)
+
+
+def scale(image, label):
+    image = tf.cast(image, tf.float32)
+    image /= 255
+    return image, label
+
+
+# get first dataset
+first_dataset = datasets["train"].map(scale).take(1)
+for image, label in first_dataset:
+    show_image(image)
+    rest_request_ambassador(
+        endpoint="localhost:32080",
+        # This prefix you can find in VirtualService in istio, command like:
+        # kubectl describe VirtualService -n submarine-user-test -l 
model-name=${model_name}
+        prefix="/seldon/submarine-user-test/1/1/",
+        arr=image,
+    )
diff --git a/dev-support/examples/quickstart/train.py 
b/dev-support/examples/quickstart/train.py
index d0d1b041..51d92cf0 100644
--- a/dev-support/examples/quickstart/train.py
+++ b/dev-support/examples/quickstart/train.py
@@ -86,6 +86,8 @@ def main():
             submarine.log_metric("accuracy", logs["accuracy"], epoch)
 
     multi_worker_model.fit(ds_train, epochs=10, steps_per_epoch=70, 
callbacks=[MyCallback()])
+    # save model
+    submarine.save_model(multi_worker_model, "tensorflow")
 
 
 if __name__ == "__main__":
diff --git a/submarine-serve/pom.xml b/submarine-serve/pom.xml
index ef4ef9be..f29d3b7c 100644
--- a/submarine-serve/pom.xml
+++ b/submarine-serve/pom.xml
@@ -46,12 +46,24 @@
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
     </dependency>
+
     <dependency>
       <groupId>org.apache.submarine</groupId>
       <artifactId>submarine-commons-utils</artifactId>
       <version>0.8.0-SNAPSHOT</version>
       <scope>test</scope>
     </dependency>
+
+    <dependency>
+      <groupId>org.apache.submarine</groupId>
+      <artifactId>submarine-k8s-utils</artifactId>
+      <version>0.8.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPDestination.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPDestination.java
index 93b888bf..ba416b25 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPDestination.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPDestination.java
@@ -22,23 +22,32 @@ import com.google.gson.annotations.SerializedName;
 import org.apache.submarine.serve.utils.IstioConstants;
 
 public class IstioHTTPDestination {
+
   @SerializedName("destination")
   private IstioDestination destination;
 
+  public IstioHTTPDestination() {
+  }
+
   public IstioHTTPDestination(String host) {
     this.destination = new IstioDestination(host);
   }
+
   public IstioHTTPDestination(String host, Integer port) {
     this.destination = new IstioDestination(host, port);
   }
 
   public static class IstioDestination {
+
     @SerializedName("host")
     private String host;
 
     @SerializedName("port")
     private IstioPort port;
 
+    public IstioDestination() {
+    }
+
     public IstioDestination(String host) {
       this(host, IstioConstants.DEFAULT_SERVE_POD_PORT);
     }
@@ -47,14 +56,50 @@ public class IstioHTTPDestination {
       this.host = host;
       this.port = new IstioPort(port);
     }
+
+    public String getHost() {
+      return host;
+    }
+
+    public void setHost(String host) {
+      this.host = host;
+    }
+
+    public IstioPort getPort() {
+      return port;
+    }
+
+    public void setPort(IstioPort port) {
+      this.port = port;
+    }
   }
 
   public static class IstioPort {
+
     @SerializedName("number")
     private Integer number;
 
+    public IstioPort() {
+    }
+
     public IstioPort(Integer port) {
       this.number = port;
     }
+
+    public Integer getNumber() {
+      return number;
+    }
+
+    public void setNumber(Integer number) {
+      this.number = number;
+    }
+  }
+
+  public IstioDestination getDestination() {
+    return destination;
+  }
+
+  public void setDestination(IstioDestination destination) {
+    this.destination = destination;
   }
 }
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
index 18e0b6c5..f587064a 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
@@ -18,22 +18,49 @@
  */
 package org.apache.submarine.serve.istio;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.gson.annotations.SerializedName;
 
 public class IstioHTTPMatchRequest {
+
   @SerializedName("uri")
+  @JsonProperty("uri")
   private IstioPrefix prefix;
 
+  public IstioHTTPMatchRequest() {
+  }
+
   public IstioHTTPMatchRequest(String prefix) {
     this.prefix = new IstioPrefix(prefix);
   }
 
   public static class IstioPrefix {
+
     @SerializedName("prefix")
+    @JsonProperty("prefix")
     private String path;
 
+    public IstioPrefix() {
+    }
+
     public IstioPrefix(String path){
       this.path = path;
     }
+
+    public String getPath() {
+      return path;
+    }
+
+    public void setPath(String path) {
+      this.path = path;
+    }
+  }
+
+  public IstioPrefix getPrefix() {
+    return prefix;
+  }
+
+  public void setPrefix(IstioPrefix prefix) {
+    this.prefix = prefix;
   }
 }
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPRoute.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPRoute.java
index f370d213..b5d0ded9 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPRoute.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPRoute.java
@@ -18,6 +18,7 @@
  */
 package org.apache.submarine.serve.istio;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.gson.annotations.SerializedName;
 import org.apache.submarine.serve.utils.IstioConstants;
 
@@ -25,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class IstioHTTPRoute {
+
   @SerializedName("match")
   private List<IstioHTTPMatchRequest> match = new ArrayList<>();
 
@@ -46,7 +48,6 @@ public class IstioHTTPRoute {
   @Override
   public String toString() {
     return "'rewrite': {'uri': " + rewrite.getRewrite() + "}";
-
   }
 
   public void addHTTPMatchRequest(IstioHTTPMatchRequest match){
@@ -56,8 +57,11 @@ public class IstioHTTPRoute {
   public void addHTTPDestination(IstioHTTPDestination destination){
     this.route.add(destination);
   }
-  public static class IstioRewrite{
+
+  public static class IstioRewrite {
+
     @SerializedName("uri")
+    @JsonProperty("uri")
     private String uri;
 
     public IstioRewrite(String rewrite){
@@ -67,6 +71,37 @@ public class IstioHTTPRoute {
     public String getRewrite() {
       return uri;
     }
+
+    public String getUri() {
+      return uri;
+    }
+
+    public void setUri(String uri) {
+      this.uri = uri;
+    }
+  }
+
+  public List<IstioHTTPMatchRequest> getMatch() {
+    return match;
+  }
+
+  public void setMatch(List<IstioHTTPMatchRequest> match) {
+    this.match = match;
+  }
+
+  public List<IstioHTTPDestination> getRoute() {
+    return route;
   }
 
+  public void setRoute(List<IstioHTTPDestination> route) {
+    this.route = route;
+  }
+
+  public IstioRewrite getRewrite() {
+    return rewrite;
+  }
+
+  public void setRewrite(IstioRewrite rewrite) {
+    this.rewrite = rewrite;
+  }
 }
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualService.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualService.java
index 75b0b1d9..608d95a4 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualService.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualService.java
@@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName;
 import io.kubernetes.client.common.KubernetesObject;
 import io.kubernetes.client.openapi.models.V1ObjectMeta;
 import org.apache.submarine.serve.utils.IstioConstants;
+import org.apache.submarine.server.k8s.utils.K8sUtils;
 
 public class IstioVirtualService implements KubernetesObject {
   @SerializedName("apiVersion")
@@ -48,12 +49,12 @@ public class IstioVirtualService implements 
KubernetesObject {
     this.spec = spec;
   }
 
-  public IstioVirtualService(String modelName, Integer modelVersion) {
+  public IstioVirtualService(Long id, String modelResourceName, Integer 
modelVersion) {
     V1ObjectMeta metadata = new V1ObjectMeta();
-    metadata.setName(modelName);
-    metadata.setNamespace(IstioConstants.DEFAULT_NAMESPACE);
+    metadata.setName(modelResourceName);
+    metadata.setNamespace(K8sUtils.getNamespace());
     setMetadata(metadata);
-    setSpec(new IstioVirtualServiceSpec(modelName, modelVersion));
+    setSpec(new IstioVirtualServiceSpec(id, modelResourceName, modelVersion));
   }
 
   public IstioVirtualService(V1ObjectMeta metadata) {
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceList.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceList.java
index ea33a2b0..22eb36a5 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceList.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceList.java
@@ -27,7 +27,10 @@ import io.kubernetes.client.common.KubernetesObject;
 import io.kubernetes.client.openapi.models.V1ListMeta;
 import org.apache.submarine.serve.utils.IstioConstants;
 
-public class IstioVirtualServiceList implements KubernetesListObject{
+public class IstioVirtualServiceList implements KubernetesListObject {
+
+  public IstioVirtualServiceList() {
+  }
 
   @SerializedName("apiVersion")
   private String apiVersion;
@@ -60,4 +63,20 @@ public class IstioVirtualServiceList implements 
KubernetesListObject{
   public String getKind() {
     return IstioConstants.KIND + "List";
   }
+
+  public void setApiVersion(String apiVersion) {
+    this.apiVersion = apiVersion;
+  }
+
+  public void setKind(String kind) {
+    this.kind = kind;
+  }
+
+  public void setMetadata(V1ListMeta metadata) {
+    this.metadata = metadata;
+  }
+
+  public void setItems(List<IstioVirtualService> items) {
+    this.items = items;
+  }
 }
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceSpec.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceSpec.java
index e8182cbb..bf63bc42 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceSpec.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioVirtualServiceSpec.java
@@ -19,30 +19,38 @@
 
 package org.apache.submarine.serve.istio;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.gson.annotations.SerializedName;
 import org.apache.submarine.serve.utils.IstioConstants;
+import org.apache.submarine.server.k8s.utils.K8sUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class IstioVirtualServiceSpec {
+
   @SerializedName("hosts")
   private List<String> hosts = new ArrayList<>();
   @SerializedName("gateways")
   private List<String> gateways = new ArrayList<>();
   @SerializedName("http")
+  @JsonProperty("http")
   private List<IstioHTTPRoute> httpRoute = new ArrayList<>();
 
   public IstioVirtualServiceSpec() {
   }
 
-  public IstioVirtualServiceSpec(String modelName, Integer modelVersion) {
+  public IstioVirtualServiceSpec(Long id, String modelResourceName, Integer 
modelVersion) {
     hosts.add(IstioConstants.DEFAULT_INGRESS_HOST);
     gateways.add(IstioConstants.DEFAULT_GATEWAY);
-    IstioHTTPDestination destination = new IstioHTTPDestination(
-        modelName + "-" + IstioConstants.DEFAULT_NAMESPACE);
-    IstioHTTPMatchRequest matchRequest = new IstioHTTPMatchRequest("/" + 
modelName
-        + "/" + String.valueOf(modelVersion) + "/");
+    // model resource name is service name
+    IstioHTTPDestination destination = new 
IstioHTTPDestination(modelResourceName);
+    IstioHTTPMatchRequest matchRequest = new IstioHTTPMatchRequest(
+        // use namespace to avoid multi tenant problems
+        // use model_version_id replaced model_name to avoid illegal 
characters and other problems
+        // use model_version as the identification of the model
+        String.format("/seldon/%s/%s/%s/", K8sUtils.getNamespace(), id, 
modelVersion)
+    );
     IstioHTTPRoute httpRoute = new IstioHTTPRoute();
     httpRoute.addHTTPDestination(destination);
     httpRoute.addHTTPMatchRequest(matchRequest);
@@ -68,4 +76,20 @@ public class IstioVirtualServiceSpec {
   public void setHTTPRoute(IstioHTTPRoute istioHTTPRoute) {
     this.httpRoute.add(istioHTTPRoute);
   }
+
+  public void setHosts(List<String> hosts) {
+    this.hosts = hosts;
+  }
+
+  public void setGateways(List<String> gateways) {
+    this.gateways = gateways;
+  }
+
+  public List<IstioHTTPRoute> getHttpRoute() {
+    return httpRoute;
+  }
+
+  public void setHttpRoute(List<IstioHTTPRoute> httpRoute) {
+    this.httpRoute = httpRoute;
+  }
 }
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/PredictorAnnotations.java
similarity index 52%
copy from 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
copy to 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/PredictorAnnotations.java
index 48e52275..472467fc 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/PredictorAnnotations.java
@@ -16,49 +16,40 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.submarine.serve.seldon;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 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;
+/**
+ * SeldonDeployment.spec.predictors[*].annotations
+ */
+public class PredictorAnnotations {
+
+  /**
+   * <a 
href="https://docs.seldon.io/projects/seldon-core/en/latest/graph/custom_svc_name.html";>
+   *   Model with Custom Service Name Annotations
+   * </a>
+   */
+  @SerializedName("seldon.io/svc-name")
+  @JsonProperty("seldon.io/svc-name")
+  private String serviceName;
+
+  /**
+   * Get predictor annotations with custom service name
+   */
+  public static PredictorAnnotations service(String serviceName) {
+    return new PredictorAnnotations()
+            .setServiceName(serviceName);
   }
 
-  public SeldonGraph getSeldonGraph() {
-    return seldonGraph;
+  public String getServiceName() {
+    return serviceName;
   }
 
-  public void setSeldonGraph(SeldonGraph seldonGraph) {
-    this.seldonGraph = seldonGraph;
+  public PredictorAnnotations setServiceName(String serviceName) {
+    this.serviceName = serviceName;
+    return this;
   }
 }
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
index 6f9a6109..b4a091e4 100644
--- 
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
@@ -18,15 +18,20 @@
  */
 package org.apache.submarine.serve.seldon;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.gson.annotations.SerializedName;
 import io.kubernetes.client.common.KubernetesObject;
 import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder;
 import org.apache.submarine.serve.utils.SeldonConstants;
-
-import java.util.ArrayList;
-import java.util.List;
+import org.apache.submarine.server.k8s.utils.K8sUtils;
 
 public class SeldonDeployment implements KubernetesObject {
+
+  public static final String MODEL_NAME_LABEL = "model-name";
+  public static final String MODEL_ID_LABEL = "model-id";
+  public static final String MODEL_VERSION_LABEL = "model-version";
+
   @SerializedName("apiVersion")
   private String apiVersion = SeldonConstants.API_VERSION;
 
@@ -39,6 +44,93 @@ public class SeldonDeployment implements KubernetesObject {
   @SerializedName("spec")
   private SeldonDeploymentSpec spec;
 
+  @JsonIgnore
+  private Long id;
+
+  @JsonIgnore
+  private String resourceName;
+
+  @JsonIgnore
+  private String modelName;
+
+  @JsonIgnore
+  private String modelURI;
+
+  @JsonIgnore
+  private Integer modelVersion;
+
+  @JsonIgnore
+  private String modelId;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public String getResourceName() {
+    return resourceName;
+  }
+
+  public void setResourceName(String resourceName) {
+    this.resourceName = resourceName;
+  }
+
+  public String getModelName() {
+    return modelName;
+  }
+
+  public void setModelName(String modelName) {
+    this.modelName = modelName;
+  }
+
+  public String getModelURI() {
+    return modelURI;
+  }
+
+  public void setModelURI(String modelURI) {
+    this.modelURI = modelURI;
+  }
+
+  public String getModelId() {
+    return modelId;
+  }
+
+  public void setModelId(String modelId) {
+    this.modelId = modelId;
+  }
+
+  public Integer getModelVersion() {
+    return modelVersion;
+  }
+
+  public void setModelVersion(Integer modelVersion) {
+    this.modelVersion = modelVersion;
+  }
+
+  public SeldonDeployment() {
+  }
+
+  public SeldonDeployment(Long id, String resourceName, String modelName, 
Integer modelVersion,
+                          String modelId, String modelURI) {
+    this.id = id;
+    this.resourceName = resourceName;
+    this.modelName = modelName;
+    this.modelVersion = modelVersion;
+    this.modelId = modelId;
+    this.modelURI = modelURI;
+
+    V1ObjectMetaBuilder metaBuilder = new V1ObjectMetaBuilder();
+    metaBuilder.withNamespace(K8sUtils.getNamespace())
+            .withName(resourceName)
+            .addToLabels(MODEL_NAME_LABEL, modelName)
+            .addToLabels(MODEL_ID_LABEL, modelId)
+            .addToLabels(MODEL_VERSION_LABEL, String.valueOf(modelVersion));
+    setMetadata(metaBuilder.build());
+  }
+
   // transient to avoid being serialized
   private transient String group = SeldonConstants.GROUP;
 
@@ -98,30 +190,11 @@ public class SeldonDeployment implements KubernetesObject {
     this.spec = seldonDeploymentSpec;
   }
 
-  public void addPredictor(SeldonPredictor seldonPredictor) {
-    this.spec.addPredictor(seldonPredictor);
+  public SeldonDeploymentSpec getSpec() {
+    return spec;
   }
 
-  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);
-    }
+  public void addPredictor(SeldonPredictor seldonPredictor) {
+    this.spec.addPredictor(seldonPredictor);
   }
 }
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/SeldonDeploymentSpec.java
similarity index 53%
copy from 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
copy to 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonDeploymentSpec.java
index 48e52275..016d063b 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonPredictor.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/SeldonDeploymentSpec.java
@@ -16,49 +16,46 @@
  * 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";
+import java.util.ArrayList;
+import java.util.List;
 
-  @SerializedName("replicas")
-  private Integer replicas = 1;
+public class SeldonDeploymentSpec {
 
-  @SerializedName("graph")
-  private SeldonGraph seldonGraph = new SeldonGraph();
+  public SeldonDeploymentSpec() {
+  }
 
-  public SeldonPredictor(String name, Integer replicas, SeldonGraph graph) {
-    setName(name);
-    setReplicas(replicas);
-    setSeldonGraph(graph);
+  public SeldonDeploymentSpec(String protocol) {
+    setProtocol(protocol);
   }
 
-  public SeldonPredictor(){};
+  @SerializedName("protocol")
+  private String protocol;
 
-  public String getName() {
-    return name;
-  }
+  @SerializedName("predictors")
+  private List<SeldonPredictor> predictors = new ArrayList<>();
 
-  public void setName(String name) {
-    this.name = name;
+  public String getProtocol() {
+    return protocol;
   }
 
-  public Integer getReplicas() {
-    return replicas;
+  public void setProtocol(String protocol) {
+    this.protocol = protocol;
   }
 
-  public void setReplicas(Integer replicas) {
-    this.replicas = replicas;
+  public void addPredictor(SeldonPredictor seldonPredictor) {
+    predictors.add(seldonPredictor);
   }
 
-  public SeldonGraph getSeldonGraph() {
-    return seldonGraph;
+  public List<SeldonPredictor> getPredictors() {
+    return predictors;
   }
 
-  public void setSeldonGraph(SeldonGraph seldonGraph) {
-    this.seldonGraph = seldonGraph;
+  public void setPredictors(List<SeldonPredictor> predictors) {
+    this.predictors = predictors;
   }
 }
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
index 48b7e25b..d38956b0 100644
--- 
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
@@ -33,6 +33,9 @@ public class SeldonGraph {
   @SerializedName("envSecretRefName")
   private String envSecretRefName = SeldonConstants.ENV_SECRET_REF_NAME;
 
+  public SeldonGraph() {
+  }
+
   public String getName() {
     return name;
   }
@@ -56,4 +59,20 @@ public class SeldonGraph {
   public void setModelUri(String modelUri) {
     this.modelUri = modelUri;
   }
+
+  public String getStorageInitializerImage() {
+    return storageInitializerImage;
+  }
+
+  public void setStorageInitializerImage(String storageInitializerImage) {
+    this.storageInitializerImage = storageInitializerImage;
+  }
+
+  public String getEnvSecretRefName() {
+    return envSecretRefName;
+  }
+
+  public void setEnvSecretRefName(String envSecretRefName) {
+    this.envSecretRefName = envSecretRefName;
+  }
 }
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
index 48e52275..56347f6e 100644
--- 
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
@@ -18,9 +18,11 @@
  */
 package org.apache.submarine.serve.seldon;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.gson.annotations.SerializedName;
 
 public class SeldonPredictor {
+
   @SerializedName("name")
   private String name = "default";
 
@@ -28,8 +30,12 @@ public class SeldonPredictor {
   private Integer replicas = 1;
 
   @SerializedName("graph")
+  @JsonProperty("graph")
   private SeldonGraph seldonGraph = new SeldonGraph();
 
+  @SerializedName("annotations")
+  private PredictorAnnotations annotations;
+
   public SeldonPredictor(String name, Integer replicas, SeldonGraph graph) {
     setName(name);
     setReplicas(replicas);
@@ -61,4 +67,13 @@ public class SeldonPredictor {
   public void setSeldonGraph(SeldonGraph seldonGraph) {
     this.seldonGraph = seldonGraph;
   }
+
+  public PredictorAnnotations getAnnotations() {
+    return annotations;
+  }
+
+  public void setAnnotations(PredictorAnnotations annotations) {
+    this.annotations = annotations;
+  }
+
 }
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/pytorch/SeldonPytorchServing.java
 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/pytorch/SeldonPytorchServing.java
similarity index 71%
rename from 
submarine-serve/src/main/java/org/apache/submarine/serve/pytorch/SeldonPytorchServing.java
rename to 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/pytorch/SeldonPytorchServing.java
index 3993ee7a..81e2844b 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/pytorch/SeldonPytorchServing.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/pytorch/SeldonPytorchServing.java
@@ -16,28 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.submarine.serve.pytorch;
+package org.apache.submarine.serve.seldon.pytorch;
 
-import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import org.apache.submarine.serve.seldon.PredictorAnnotations;
 import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.serve.seldon.SeldonDeploymentSpec;
 import org.apache.submarine.serve.seldon.SeldonGraph;
 import org.apache.submarine.serve.seldon.SeldonPredictor;
 import org.apache.submarine.serve.utils.SeldonConstants;
 
 public class SeldonPytorchServing extends SeldonDeployment {
-  public SeldonPytorchServing(String name, String modelURI){
-    V1ObjectMeta v1ObjectMeta = new V1ObjectMeta();
-    v1ObjectMeta.setName(name);
-    v1ObjectMeta.setNamespace(SeldonConstants.DEFAULT_NAMESPACE);
-    setMetadata(v1ObjectMeta);
+
+  public SeldonPytorchServing() {
+  }
+
+  public SeldonPytorchServing(Long id, String resourceName, String modelName, 
Integer modelVersion,
+                              String modelId, String modelURI) {
+    super(id, resourceName, modelName, modelVersion, modelId, modelURI);
 
     setSpec(new SeldonDeploymentSpec(SeldonConstants.KFSERVING_PROTOCOL));
 
     SeldonGraph seldonGraph = new SeldonGraph();
-    seldonGraph.setName(name);
+    seldonGraph.setName(String.format("version-%s", modelVersion));
     seldonGraph.setImplementation(SeldonConstants.TRITON_IMPLEMENTATION);
     seldonGraph.setModelUri(modelURI);
     SeldonPredictor seldonPredictor = new SeldonPredictor();
+    seldonPredictor.setAnnotations(PredictorAnnotations.service(resourceName));
     seldonPredictor.setSeldonGraph(seldonGraph);
 
     addPredictor(seldonPredictor);
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/seldon/tensorflow/SeldonTFServing.java
similarity index 71%
rename from 
submarine-serve/src/main/java/org/apache/submarine/serve/tensorflow/SeldonTFServing.java
rename to 
submarine-serve/src/main/java/org/apache/submarine/serve/seldon/tensorflow/SeldonTFServing.java
index 91565fd8..ebea68ec 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/tensorflow/SeldonTFServing.java
+++ 
b/submarine-serve/src/main/java/org/apache/submarine/serve/seldon/tensorflow/SeldonTFServing.java
@@ -16,28 +16,32 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.submarine.serve.tensorflow;
+package org.apache.submarine.serve.seldon.tensorflow;
 
-import io.kubernetes.client.openapi.models.V1ObjectMeta;
+import org.apache.submarine.serve.seldon.PredictorAnnotations;
 import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.serve.seldon.SeldonDeploymentSpec;
 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);
+
+  public SeldonTFServing() {
+  }
+
+  public SeldonTFServing(Long id, String resourceName, String modelName, 
Integer modelVersion,
+                         String modelId, String modelURI) {
+    super(id, resourceName, modelName, modelVersion, modelId, modelURI);
 
     setSpec(new SeldonDeploymentSpec(SeldonConstants.SELDON_PROTOCOL));
 
     SeldonGraph seldonGraph = new SeldonGraph();
-    seldonGraph.setName(name);
+    seldonGraph.setName(String.format("version-%s", modelVersion));
     seldonGraph.setImplementation(SeldonConstants.TFSERVING_IMPLEMENTATION);
     seldonGraph.setModelUri(modelURI);
     SeldonPredictor seldonPredictor = new SeldonPredictor();
+    seldonPredictor.setAnnotations(PredictorAnnotations.service(resourceName));
     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
index e41a5646..e54d621a 100644
--- 
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
@@ -33,8 +33,6 @@ public class SeldonConstants {
 
   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";
 
   public static final String KFSERVING_PROTOCOL = "kfserving";
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
index 5fa11ad0..10ca17d2 100644
--- 
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
@@ -19,12 +19,22 @@
 package org.apache.submarine.server.api.model;
 
 public class ServeSpec {
+
+  private Long id;
   private String modelName;
   private Integer modelVersion;
   private String modelId;
   private String modelType;
   private String modelURI;
 
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
   public String getModelName() {
     return modelName;
   }
diff --git a/submarine-server/server-core/pom.xml 
b/submarine-server/server-core/pom.xml
index d33ff945..311ee5a6 100644
--- a/submarine-server/server-core/pom.xml
+++ b/submarine-server/server-core/pom.xml
@@ -343,9 +343,19 @@
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
         </exclusion>
+        <exclusion>
+          <groupId>commons-codec</groupId>
+          <artifactId>commons-codec</artifactId>
+        </exclusion>
       </exclusions>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>${commons-codec.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
diff --git 
a/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/ModelVersionEntity.java
 
b/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/ModelVersionEntity.java
index e5a945b5..90da814a 100644
--- 
a/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/ModelVersionEntity.java
+++ 
b/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/ModelVersionEntity.java
@@ -23,6 +23,9 @@ import java.sql.Timestamp;
 import java.util.List;
 
 public class ModelVersionEntity {
+
+  private Long modelVersionId;
+
   private String name;
 
   private Integer version;
@@ -47,6 +50,14 @@ public class ModelVersionEntity {
 
   private List<String> tags;
 
+  public Long getModelVersionId() {
+    return modelVersionId;
+  }
+
+  public void setModelVersionId(Long modelVersionId) {
+    this.modelVersionId = modelVersionId;
+  }
+
   public String getName() {
     return name;
   }
@@ -147,7 +158,8 @@ public class ModelVersionEntity {
 
   public String toString() {
     return "ModelVersionEntity{" +
-      "name='" + name + '\'' +
+      "modelVersionId=" + modelVersionId +
+      ", name='" + name + '\'' +
       ", version='" + version + '\'' +
       ", id='" + id + '\'' +
       ", userId='" + userId + '\'' +
diff --git 
a/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/RegisteredModelEntity.java
 
b/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/RegisteredModelEntity.java
index e52cac7d..a18c1a18 100644
--- 
a/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/RegisteredModelEntity.java
+++ 
b/submarine-server/server-database/src/main/java/org/apache/submarine/server/database/model/entities/RegisteredModelEntity.java
@@ -24,6 +24,8 @@ import java.util.List;
 
 public class RegisteredModelEntity {
 
+  private Long modelId;
+
   private String name;
 
   private Timestamp creationTime;
@@ -34,6 +36,14 @@ public class RegisteredModelEntity {
 
   private List<String> tags;
 
+  public Long getModelId() {
+    return modelId;
+  }
+
+  public void setModelId(Long modelId) {
+    this.modelId = modelId;
+  }
+
   public String getName() {
     return name;
   }
@@ -78,7 +88,8 @@ public class RegisteredModelEntity {
 
   public String toString() {
     return "RegisteredModelEntity{" +
-        "name='" + name + '\'' +
+        "modelId=" + modelId +
+        ", name='" + name + '\'' +
         ", createTime='" + creationTime + '\'' +
         ", lastUpdatedTime=" + lastUpdatedTime + '\'' +
         ", description='" + description + '\'' +
diff --git 
a/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/ModelVersionMapper.xml
 
b/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/ModelVersionMapper.xml
index e16e95ae..d868e857 100644
--- 
a/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/ModelVersionMapper.xml
+++ 
b/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/ModelVersionMapper.xml
@@ -20,6 +20,7 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd";>
 <mapper 
namespace="org.apache.submarine.server.database.model.mappers.ModelVersionMapper">
   <resultMap id="resultMap" 
type="org.apache.submarine.server.database.model.entities.ModelVersionEntity">
+    <result column="model_version_id" property="modelVersionId" />
     <result column="name" property="name" />
     <result column="version" property="version" />
     <result column="id" property="id" />
@@ -34,6 +35,7 @@
   </resultMap>
 
   <resultMap id="resultMapWithTag" 
type="org.apache.submarine.server.database.model.entities.ModelVersionEntity">
+    <result column="model_version_id" property="modelVersionId" />
     <result column="name" property="name" />
     <result column="version" property="version" />
     <result column="id" property="id" />
@@ -51,7 +53,7 @@
   </resultMap>
 
   <sql id="Base_Column_List">
-    name, version, id, user_id, experiment_id, model_type, current_stage, 
creation_time,
+    model_version_id, name, version, id, user_id, experiment_id, model_type, 
current_stage, creation_time,
     last_updated_time, dataset, description
   </sql>
 
@@ -76,7 +78,7 @@
     where mv.name = #{name,jdbcType=VARCHAR}
   </select>
 
-  <insert id="insert" 
parameterType="org.apache.submarine.server.database.model.entities.ModelVersionEntity">
+  <insert id="insert" 
parameterType="org.apache.submarine.server.database.model.entities.ModelVersionEntity"
 useGeneratedKeys="true" keyProperty="modelVersionId">
     insert into model_version (name, version, id, user_id, experiment_id, 
model_type, current_stage, creation_time, last_updated_time, dataset, 
description)
     values (#{name,jdbcType=VARCHAR}, #{version,jdbcType=INTEGER}, 
#{id,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR},
             #{experimentId,jdbcType=VARCHAR}, #{modelType,jdbcType=VARCHAR}, 
#{currentStage,jdbcType=VARCHAR},
diff --git 
a/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/RegisteredModelMapper.xml
 
b/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/RegisteredModelMapper.xml
index eb165858..dd858569 100644
--- 
a/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/RegisteredModelMapper.xml
+++ 
b/submarine-server/server-database/src/main/resources/org/apache/submarine/database/mappers/RegisteredModelMapper.xml
@@ -25,6 +25,7 @@
   </resultMap>
 
   <resultMap id="resultMap" 
type="org.apache.submarine.server.database.model.entities.RegisteredModelEntity">
+    <result column="model_id" property="modelId" />
     <result column="name" property="name" />
     <result column="creation_time" property="creationTime" />
     <result column="last_updated_time" property="lastUpdatedTime" />
@@ -32,6 +33,7 @@
   </resultMap>
 
   <resultMap id="resultMapWithTag" 
type="org.apache.submarine.server.database.model.entities.RegisteredModelEntity">
+    <result column="model_id" property="modelId" />
     <result column="name" property="name" />
     <result column="creation_time" property="creationTime" />
     <result column="last_updated_time" property="lastUpdatedTime" />
@@ -42,7 +44,7 @@
   </resultMap>
 
   <sql id="Base_Column_List">
-    name, creation_time, last_updated_time, description
+    model_id, name, creation_time, last_updated_time, description
   </sql>
 
   <select id="select" parameterType="java.lang.String" resultMap="resultMap">
@@ -63,7 +65,7 @@
     from registered_model rm left join registered_model_tag tag on tag.name = 
rm.name
   </select>
 
-  <insert id="insert" 
parameterType="org.apache.submarine.server.database.model.entities.RegisteredModelEntity">
+  <insert id="insert" 
parameterType="org.apache.submarine.server.database.model.entities.RegisteredModelEntity"
 useGeneratedKeys="true" keyProperty="id">
     insert into registered_model (name, creation_time, last_updated_time, 
description)
     values (#{name,jdbcType=VARCHAR}, NOW(3), NOW(3), 
#{description,jdbcType=VARCHAR});
     <if test="tags != null and !tags.isEmpty()">
diff --git a/submarine-server/server-submitter/submitter-k8s/pom.xml 
b/submarine-server/server-submitter/submitter-k8s/pom.xml
index 96e518b4..810090b2 100644
--- a/submarine-server/server-submitter/submitter-k8s/pom.xml
+++ b/submarine-server/server-submitter/submitter-k8s/pom.xml
@@ -100,11 +100,11 @@
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
     </dependency>
+
     <dependency>
         <groupId>org.apache.submarine</groupId>
         <artifactId>submarine-serve</artifactId>
         <version>${project.version}</version>
-        <scope>compile</scope>
     </dependency>
 
     <dependency>
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 a550cec1..755298c7 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
@@ -31,7 +31,6 @@ import io.kubernetes.client.openapi.models.V1Deployment;
 import io.kubernetes.client.openapi.models.V1ObjectMeta;
 import io.kubernetes.client.openapi.models.V1Pod;
 import io.kubernetes.client.openapi.models.V1PodList;
-import io.kubernetes.client.util.generic.options.CreateOptions;
 import io.kubernetes.client.util.generic.options.DeleteOptions;
 import io.kubernetes.client.util.generic.options.ListOptions;
 
@@ -39,9 +38,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.submarine.commons.utils.SubmarineConfVars;
 import org.apache.submarine.commons.utils.SubmarineConfiguration;
 import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
-import org.apache.submarine.serve.pytorch.SeldonPytorchServing;
-import org.apache.submarine.serve.seldon.SeldonDeployment;
-import org.apache.submarine.serve.tensorflow.SeldonTFServing;
 import org.apache.submarine.server.k8s.utils.K8sUtils;
 import org.apache.submarine.server.api.Submitter;
 import org.apache.submarine.server.api.common.CustomResourceType;
@@ -66,6 +62,8 @@ import 
org.apache.submarine.server.submitter.k8s.model.common.PersistentVolumeCl
 import org.apache.submarine.server.submitter.k8s.model.mljob.MLJob;
 import org.apache.submarine.server.submitter.k8s.model.mljob.MLJobFactory;
 import org.apache.submarine.server.submitter.k8s.model.notebook.NotebookCR;
+import 
org.apache.submarine.server.submitter.k8s.model.seldon.SeldonDeploymentFactory;
+import org.apache.submarine.server.submitter.k8s.model.seldon.SeldonResource;
 import org.apache.submarine.server.submitter.k8s.util.NotebookUtils;
 import org.apache.submarine.server.submitter.k8s.util.OwnerReferenceUtils;
 
@@ -423,48 +421,23 @@ public class K8sSubmitter implements Submitter {
   }
 
   @Override
-  public void createServe(ServeSpec spec)
-          throws SubmarineRuntimeException {
-    SeldonDeployment seldonDeployment = parseServeSpec(spec);
-    IstioVirtualService istioVirtualService = new 
IstioVirtualService(spec.getModelName(),
-            spec.getModelVersion());
-    try {
-      k8sClient.getSeldonDeploymentClient().create("default", seldonDeployment,
-              new CreateOptions()).throwsApiException();
-    } catch (ApiException e) {
-      LOG.error(e.getMessage(), e);
-      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
-    }
-    try {
-      k8sClient.getIstioVirtualServiceClient().create("default", 
istioVirtualService, new CreateOptions())
-              .throwsApiException();
-    } catch (ApiException e) {
-      LOG.error(e.getMessage(), e);
-      try {
-        k8sClient.getSeldonDeploymentClient().delete("default", 
seldonDeployment.getMetadata().getName(),
-                
getDeleteOptions(seldonDeployment.getApiVersion())).throwsApiException();
-      } catch (ApiException e1) {
-        LOG.error(e1.getMessage(), e1);
-      }
-      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
-    }
+  public void createServe(ServeSpec spec) throws SubmarineRuntimeException {
+    // Seldon Deployment Resource
+    SeldonResource seldonDeployment = 
SeldonDeploymentFactory.getSeldonDeployment(spec);
+    // VirtualService Resource
+    IstioVirtualService istioVirtualService = 
seldonDeployment.getIstioVirtualService();
+    // commit SeldonResource and IstioVirtualService with transaction
+    resourceTransaction(seldonDeployment, istioVirtualService);
   }
 
   @Override
-  public void deleteServe(ServeSpec spec)
-          throws SubmarineRuntimeException {
-    SeldonDeployment seldonDeployment = parseServeSpec(spec);
-    IstioVirtualService istioVirtualService = new 
IstioVirtualService(spec.getModelName(),
-            spec.getModelVersion());
-    try {
-      k8sClient.getSeldonDeploymentClient().delete("default", 
seldonDeployment.getMetadata().getName(),
-              
getDeleteOptions(seldonDeployment.getApiVersion())).throwsApiException();
-      k8sClient.getIstioVirtualServiceClient().delete("default", 
istioVirtualService.getMetadata().getName(),
-              
getDeleteOptions(istioVirtualService.getApiVersion())).throwsApiException();
-    } catch (ApiException e) {
-      LOG.error(e.getMessage(), e);
-      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
-    }
+  public void deleteServe(ServeSpec spec) throws SubmarineRuntimeException {
+    // Seldon Deployment Resource
+    SeldonResource seldonDeployment = 
SeldonDeploymentFactory.getSeldonDeployment(spec);
+    // VirtualService Resource
+    IstioVirtualService istioVirtualService = 
seldonDeployment.getIstioVirtualService();
+    // Delete SeldonResource and IstioVirtualService with transaction
+    deleteResourcesTransaction(seldonDeployment, istioVirtualService);
   }
 
   private String getJobLabelSelector(ExperimentSpec experimentSpec) {
@@ -480,20 +453,4 @@ public class K8sSubmitter implements Submitter {
     }
   }
 
-  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")){
-      seldonDeployment = new SeldonPytorchServing(modelName, modelURI);
-    } else {
-      throw new SubmarineRuntimeException("Given serve type: " + modelType + " 
is not supported.");
-    }
-    return seldonDeployment;
-  }
-
 }
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/istio/IstioVirtualService.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/istio/IstioVirtualService.java
index 50eb9ebf..6a35cffd 100644
--- 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/istio/IstioVirtualService.java
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/istio/IstioVirtualService.java
@@ -44,8 +44,8 @@ public class IstioVirtualService extends 
org.apache.submarine.serve.istio.IstioV
     super(metadata, spec);
   }
 
-  public IstioVirtualService(String modelName, Integer modelVersion) {
-    super(modelName, modelVersion);
+  public IstioVirtualService(Long id, String modelResourceName, Integer 
modelVersion) {
+    super(id, modelResourceName, modelVersion);
   }
 
   public IstioVirtualService(V1ObjectMeta metadata) {
@@ -72,7 +72,7 @@ public class IstioVirtualService extends 
org.apache.submarine.serve.istio.IstioV
       return this;
     } catch (ApiException e) {
       LOG.error("K8s submitter: Create notebook VirtualService custom resource 
object failed by " +
-              e.getMessage(), e);
+          e.getMessage(), e);
       throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
     } catch (JsonSyntaxException e) {
       LOG.error("K8s submitter: parse response object failed by " + 
e.getMessage(), e);
@@ -90,7 +90,7 @@ public class IstioVirtualService extends 
org.apache.submarine.serve.istio.IstioV
     try {
       if (LOG.isDebugEnabled()) {
         LOG.debug("Delete VirtualService resource in namespace: {} and name: 
{}",
-                this.getMetadata().getNamespace(), 
this.getMetadata().getName());
+            this.getMetadata().getNamespace(), this.getMetadata().getName());
       }
       api.getIstioVirtualServiceClient()
         .delete(
@@ -100,7 +100,7 @@ public class IstioVirtualService extends 
org.apache.submarine.serve.istio.IstioV
         ).throwsApiException();
     } catch (ApiException e) {
       LOG.error("K8s submitter: Delete notebook VirtualService custom resource 
object failed by " +
-              e.getMessage(), e);
+          e.getMessage(), e);
       K8sSubmitter.API_EXCEPTION_404_CONSUMER.apply(e);
     }
     return this;
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentFactory.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentFactory.java
new file mode 100644
index 00000000..fab414fe
--- /dev/null
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.submitter.k8s.model.seldon;
+
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.server.api.model.ServeSpec;
+
+/**
+ * SeldonDeployment K8s Model Resource Factory
+ */
+public class SeldonDeploymentFactory {
+
+  /**
+   * Get SeldonDeployment by model type
+   */
+  public static SeldonResource getSeldonDeployment(ServeSpec spec) throws 
SubmarineRuntimeException {
+    Long id = spec.getId();
+    String modelId = spec.getModelId();
+    String resourceName = String.format("submarine-model-%s-%s", spec.getId(), 
modelId);
+
+    String modelName = spec.getModelName();
+    String modelType = spec.getModelType();
+    String modelURI = spec.getModelURI();
+    Integer modelVersion = spec.getModelVersion();
+
+    switch (modelType) {
+      case "tensorflow":
+        return new SeldonDeploymentTFServing(id, resourceName, modelName, 
modelVersion,
+            modelId, modelURI);
+      case "pytorch":
+        return new SeldonDeploymentPytorchServing(id, resourceName, modelName, 
modelVersion,
+            modelId, modelURI);
+      case "xgboost":// TODO(cdmikechen): Will fix 
https://issues.apache.org/jira/browse/SUBMARINE-1316
+      default:
+        throw new SubmarineRuntimeException("Given serve type: " + modelType + 
" is not supported.");
+    }
+  }
+}
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentPytorchServing.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentPytorchServing.java
new file mode 100644
index 00000000..4082cc3c
--- /dev/null
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentPytorchServing.java
@@ -0,0 +1,107 @@
+/*
+ * 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.submitter.k8s.model.seldon;
+
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.options.CreateOptions;
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.serve.seldon.pytorch.SeldonPytorchServing;
+import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.server.submitter.k8s.client.K8sClient;
+import 
org.apache.submarine.server.submitter.k8s.model.istio.IstioVirtualService;
+import org.apache.submarine.server.submitter.k8s.util.OwnerReferenceUtils;
+import org.apache.submarine.server.submitter.k8s.util.YamlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.apache.submarine.server.submitter.k8s.K8sSubmitter.getDeleteOptions;
+
+/**
+ * Seldon Deployment Pytorch Serving Resource
+ */
+public class SeldonDeploymentPytorchServing extends SeldonPytorchServing 
implements SeldonResource {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(SeldonDeploymentPytorchServing.class);
+
+  public SeldonDeploymentPytorchServing() {
+  }
+
+  public SeldonDeploymentPytorchServing(Long id, String resourceName, String 
modelName, Integer modelVersion,
+                                        String modelId, String modelURI) {
+    super(id, resourceName, modelName, modelVersion, modelId, modelURI);
+    // add owner reference so that we can automatically delete it when 
submarine CR has been deleted
+    getMetadata().setOwnerReferences(OwnerReferenceUtils.getOwnerReference());
+  }
+
+  @Override
+  public SeldonDeployment read(K8sClient api) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public SeldonDeployment create(K8sClient api) {
+    try {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Create Seldon PytorchServing resource: \n{}", 
YamlUtils.toPrettyYaml(this));
+      }
+      api.getSeldonDeploymentClient()
+          .create(getMetadata().getNamespace(), this, new CreateOptions())
+          .throwsApiException();
+      return this;
+    } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
+      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+    }
+  }
+
+  @Override
+  public SeldonDeployment replace(K8sClient api) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public SeldonDeployment delete(K8sClient api) {
+    try {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Delete Seldon PytorchServing resource in namespace: {} and 
name: {}",
+            this.getMetadata().getNamespace(), this.getMetadata().getName());
+      }
+      api.getSeldonDeploymentClient()
+          .delete(getMetadata().getNamespace(), getMetadata().getName(),
+              getDeleteOptions(getApiVersion()))
+          .throwsApiException();
+      return this;
+    } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
+      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+    }
+  }
+
+  @Override
+  public IstioVirtualService getIstioVirtualService() {
+    IstioVirtualService service = new IstioVirtualService(
+        getId(), getMetadata().getName(), getModelVersion()
+    );
+    service.getMetadata().putLabelsItem(MODEL_NAME_LABEL, getModelName());
+    service.getMetadata().putLabelsItem(MODEL_ID_LABEL, getModelId());
+    service.getMetadata().putLabelsItem(MODEL_VERSION_LABEL, 
String.valueOf(getModelVersion()));
+    return service;
+  }
+}
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentTFServing.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentTFServing.java
new file mode 100644
index 00000000..44518f25
--- /dev/null
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonDeploymentTFServing.java
@@ -0,0 +1,106 @@
+/*
+ * 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.submitter.k8s.model.seldon;
+
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.util.generic.options.CreateOptions;
+import org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
+import org.apache.submarine.serve.seldon.tensorflow.SeldonTFServing;
+import org.apache.submarine.server.submitter.k8s.client.K8sClient;
+import 
org.apache.submarine.server.submitter.k8s.model.istio.IstioVirtualService;
+import org.apache.submarine.server.submitter.k8s.util.OwnerReferenceUtils;
+import org.apache.submarine.server.submitter.k8s.util.YamlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.apache.submarine.server.submitter.k8s.K8sSubmitter.getDeleteOptions;
+
+/**
+ * Seldon Deployment Tensorflow Serving Resource
+ */
+public class SeldonDeploymentTFServing extends SeldonTFServing implements 
SeldonResource {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(SeldonDeploymentTFServing.class);
+
+  public SeldonDeploymentTFServing() {
+  }
+
+  public SeldonDeploymentTFServing(Long id, String resourceName, String 
modelName, Integer modelVersion,
+                                   String modelId, String modelURI) {
+    super(id, resourceName, modelName, modelVersion, modelId, modelURI);
+    // add owner reference so that we can automatically delete it when 
submarine CR has been deleted
+    getMetadata().setOwnerReferences(OwnerReferenceUtils.getOwnerReference());
+  }
+
+  @Override
+  public SeldonTFServing read(K8sClient api) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public SeldonTFServing create(K8sClient api) {
+    try {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Create Seldon TFServing resource: \n{}", 
YamlUtils.toPrettyYaml(this));
+      }
+      api.getSeldonDeploymentClient()
+          .create(getMetadata().getNamespace(), this, new CreateOptions())
+          .throwsApiException();
+      return this;
+    } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
+      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+    }
+  }
+
+  @Override
+  public SeldonTFServing replace(K8sClient api) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public SeldonTFServing delete(K8sClient api) {
+    try {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Delete Seldon TFServing resource in namespace: {} and name: 
{}",
+            this.getMetadata().getNamespace(), this.getMetadata().getName());
+      }
+      api.getSeldonDeploymentClient()
+          .delete(getMetadata().getNamespace(), getMetadata().getName(),
+              getDeleteOptions(getApiVersion()))
+          .throwsApiException();
+      return this;
+    } catch (ApiException e) {
+      LOG.error(e.getMessage(), e);
+      throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+    }
+  }
+
+  @Override
+  public IstioVirtualService getIstioVirtualService() {
+    IstioVirtualService service = new IstioVirtualService(
+        getId(), getMetadata().getName(), getModelVersion()
+    );
+    service.getMetadata().putLabelsItem(MODEL_NAME_LABEL, getModelName());
+    service.getMetadata().putLabelsItem(MODEL_ID_LABEL, getModelId());
+    service.getMetadata().putLabelsItem(MODEL_VERSION_LABEL, 
String.valueOf(getModelVersion()));
+    return service;
+  }
+}
diff --git 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonResource.java
similarity index 56%
copy from 
submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
copy to 
submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonResource.java
index 18e0b6c5..b304fe94 100644
--- 
a/submarine-serve/src/main/java/org/apache/submarine/serve/istio/IstioHTTPMatchRequest.java
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/model/seldon/SeldonResource.java
@@ -16,24 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.submarine.serve.istio;
 
-import com.google.gson.annotations.SerializedName;
+package org.apache.submarine.server.submitter.k8s.model.seldon;
 
-public class IstioHTTPMatchRequest {
-  @SerializedName("uri")
-  private IstioPrefix prefix;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.submarine.serve.seldon.SeldonDeployment;
+import org.apache.submarine.server.submitter.k8s.model.K8sResource;
+import 
org.apache.submarine.server.submitter.k8s.model.istio.IstioVirtualService;
 
-  public IstioHTTPMatchRequest(String prefix) {
-    this.prefix = new IstioPrefix(prefix);
-  }
+public interface SeldonResource extends K8sResource<SeldonDeployment> {
 
-  public static class IstioPrefix {
-    @SerializedName("prefix")
-    private String path;
+  /**
+   * Get IstioVirtualService, Using the name directly may result in illegal 
characters,
+   * so we need to do a conversion using the resource name of the 
SeldonResource
+   */
+  @JsonIgnore
+  IstioVirtualService getIstioVirtualService();
 
-    public IstioPrefix(String path){
-      this.path = path;
-    }
-  }
 }
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/YamlUtils.java
 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/YamlUtils.java
index b50d180b..06d0ea1c 100644
--- 
a/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/YamlUtils.java
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/main/java/org/apache/submarine/server/submitter/k8s/util/YamlUtils.java
@@ -50,6 +50,9 @@ public class YamlUtils {
             .setSerializationInclusion(JsonInclude.Include.NON_NULL);
   }
 
+  /**
+   * Pretty yaml
+   */
   public static String toPrettyYaml(Object pojoObject) {
     try {
       return YAML_MAPPER.writeValueAsString(pojoObject);
@@ -57,4 +60,15 @@ public class YamlUtils {
       throw new RuntimeException("Parse yaml failed! " + 
pojoObject.getClass().getName(), ex);
     }
   }
+
+  /**
+   * Read yaml to class
+   */
+  public static <T> T readValue(String content, Class<T> tClass) {
+    try {
+      return YAML_MAPPER.readValue(content, tClass);
+    } catch (JsonProcessingException ex) {
+      throw new RuntimeException("Read yaml failed! " + tClass.getName(), ex);
+    }
+  }
 }
diff --git 
a/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/seldon/SeldonDeploymentResourceTest.java
 
b/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/seldon/SeldonDeploymentResourceTest.java
new file mode 100644
index 00000000..a18697ae
--- /dev/null
+++ 
b/submarine-server/server-submitter/submitter-k8s/src/test/java/org/apache/submarine/server/submitter/k8s/seldon/SeldonDeploymentResourceTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.submitter.k8s.seldon;
+
+import org.apache.submarine.server.api.model.ServeSpec;
+import 
org.apache.submarine.server.submitter.k8s.model.seldon.SeldonDeploymentFactory;
+import 
org.apache.submarine.server.submitter.k8s.model.seldon.SeldonDeploymentPytorchServing;
+import 
org.apache.submarine.server.submitter.k8s.model.seldon.SeldonDeploymentTFServing;
+import org.apache.submarine.server.submitter.k8s.model.seldon.SeldonResource;
+import org.apache.submarine.server.submitter.k8s.util.YamlUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test Seldon Deployment Resource
+ */
+public class SeldonDeploymentResourceTest {
+
+  @Test
+  public void testSeldonDeploymentTFServingResourceToYaml() {
+    ServeSpec serveSpec = new ServeSpec();
+    serveSpec.setId(1L);
+    serveSpec.setModelVersion(1);
+    serveSpec.setModelName("test-model");
+    serveSpec.setModelType("tensorflow");
+    serveSpec.setModelId("5f0a43f251064fa8979660eddf04ede8");
+    serveSpec.setModelURI("s3://submarine/registry/" +
+        "test-model-1-5f0a43f251064fa8979660eddf04ede8/test-model");
+
+    // to yaml
+    SeldonResource seldonDeployment = 
SeldonDeploymentFactory.getSeldonDeployment(serveSpec);
+    String yaml = YamlUtils.toPrettyYaml(seldonDeployment);
+    System.out.println(yaml);
+
+    // cast to object
+    SeldonDeploymentTFServing sdtfs = YamlUtils.readValue(yaml, 
SeldonDeploymentTFServing.class);
+    Assert.assertEquals("submarine-model-1-5f0a43f251064fa8979660eddf04ede8",
+        sdtfs.getMetadata().getName());
+    Assert.assertEquals(1, sdtfs.getSpec().getPredictors().size());
+    Assert.assertEquals("seldon", sdtfs.getSpec().getProtocol());
+    Assert.assertEquals("version-1", 
sdtfs.getSpec().getPredictors().get(0).getSeldonGraph().getName());
+  }
+
+  @Test
+  public void testSeldonDeploymentPytorchServingResourceToYaml() {
+    ServeSpec serveSpec = new ServeSpec();
+    serveSpec.setId(2L);
+    serveSpec.setModelVersion(5);
+    serveSpec.setModelName("test-model");
+    serveSpec.setModelType("pytorch");
+    serveSpec.setModelId("5f0a43f251064fa8979660eddf04ede8");
+    serveSpec.setModelURI("s3://submarine/registry/" +
+            "test-model-1-5f0a43f251064fa8979660eddf04ede8/test-model");
+
+    // to yaml
+    SeldonResource seldonDeployment = 
SeldonDeploymentFactory.getSeldonDeployment(serveSpec);
+    String yaml = YamlUtils.toPrettyYaml(seldonDeployment);
+    System.out.println(yaml);
+
+    // cast to object
+    SeldonDeploymentPytorchServing sdpts = YamlUtils.readValue(yaml, 
SeldonDeploymentPytorchServing.class);
+    Assert.assertEquals("submarine-model-2-5f0a43f251064fa8979660eddf04ede8",
+            sdpts.getMetadata().getName());
+    Assert.assertEquals("kfserving", sdpts.getSpec().getProtocol());
+    Assert.assertEquals(1, sdpts.getSpec().getPredictors().size());
+    Assert.assertEquals("version-5", 
sdpts.getSpec().getPredictors().get(0).getSeldonGraph().getName());
+  }
+
+}
diff --git a/submarine-workbench/workbench-web/src/app/interfaces/model-info.ts 
b/submarine-workbench/workbench-web/src/app/interfaces/model-info.ts
index 7fe01b99..1a0fbd49 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/model-info.ts
+++ b/submarine-workbench/workbench-web/src/app/interfaces/model-info.ts
@@ -20,7 +20,7 @@
 export interface ModelInfo {
   name: string;
   creationTime: string,
-  lastUpdatedTime: string, 
+  lastUpdatedTime: string,
   tags: string[],
   description: string,
-}
\ No newline at end of file
+}
diff --git 
a/submarine-workbench/workbench-web/src/app/interfaces/model-serve.ts 
b/submarine-workbench/workbench-web/src/app/interfaces/model-serve.ts
index 4853e3d4..8882fbdb 100644
--- a/submarine-workbench/workbench-web/src/app/interfaces/model-serve.ts
+++ b/submarine-workbench/workbench-web/src/app/interfaces/model-serve.ts
@@ -17,8 +17,8 @@
  * under the License.
  */
 
-
 export interface ServeSpec {
+    id: number,
     modelName: string,
     modelVersion: number,
-}
\ No newline at end of file
+}
diff --git 
a/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.html
 
b/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.html
index 1e422d29..7f87d11a 100644
--- 
a/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.html
+++ 
b/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.html
@@ -85,7 +85,7 @@
             nzTitle="Are you sure you want to serve the model?"
             nzCancelText="Cancel"
             nzOkText="Ok"
-            (nzOnConfirm)="onCreateServe(data.version)"
+            (nzOnConfirm)="onCreateServe(data.modelVersionId, data.version)"
             (click)="preventEvent($event)"
           >
             <i id="icon-createServe{{ i }}" nz-icon nzType="play-circle" 
nzTheme="fill" class="model-info-icon" ></i>
@@ -99,7 +99,7 @@
             nzTitle="Are you sure you want to delete the model serve?"
             nzCancelText="Cancel"
             nzOkText="Ok"
-            (nzOnConfirm)="onDeleteServe(data.version)"
+            (nzOnConfirm)="onDeleteServe(data.modelVersionId, data.version)"
             (click)="preventEvent($event)"
           >
             <i id="icon-pause{{ i }}" nz-icon nzType="pause-circle" 
nzTheme="fill" class="model-info-icon"></i>
@@ -115,7 +115,7 @@
             nzCancelText="Cancel"
             nzOkText="Ok"
             (nzOnConfirm)="onDeleteModelVersion(data.version)"
-            (click)="preventEvent($event)"  
+            (click)="preventEvent($event)"
           >
             <i nz-icon nzType="delete" nzTheme="fill" class="model-info-icon" 
></i>
           </a>
@@ -133,4 +133,4 @@
       </tr>
     </tbody>
   </nz-table>
-</div>
\ No newline at end of file
+</div>
diff --git 
a/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.ts
 
b/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.ts
index b9818066..97218f18 100644
--- 
a/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.ts
+++ 
b/submarine-workbench/workbench-web/src/app/pages/workbench/model/model-info/model-info.component.ts
@@ -37,15 +37,15 @@ export class ModelInfoComponent implements OnInit {
   isModelInfoLoading: boolean = true;
   isModelVersionsLoading: boolean = true;
   modelName: string;
-  selectedModelInfo: ModelInfo; 
+  selectedModelInfo: ModelInfo;
   modelVersions: ModelVersionInfo[];
   humanizedCreationTime: string;
   humanizedLastUpdatedTime: string;
 
   constructor(
-    private router: Router, 
-    private route: ActivatedRoute, 
-    private modelVersionService: ModelVersionService, 
+    private router: Router,
+    private route: ActivatedRoute,
+    private modelVersionService: ModelVersionService,
     private modelService: ModelService,
     private modelServeService: ModelServeService,
     private nzMessageService: NzMessageService,
@@ -78,10 +78,12 @@ export class ModelInfoComponent implements OnInit {
     );
   }
 
-  onCreateServe = (version: number) => {
-    this.modelServeService.createServe(this.modelName, version).subscribe({
+  onCreateServe = (id: number, version: number) => {
+    this.modelServeService.createServe(id, this.modelName, version).subscribe({
       next: (result) => {
         this.nzMessageService.success(`The model serve with name: 
${this.modelName} and version: ${version} is created.`)
+        // refresh model version status after created serve
+        this.fetchModelAllVersions();
       },
       error: (msg) => {
         this.nzMessageService.error(`${msg}, please try again`, {
@@ -91,10 +93,12 @@ export class ModelInfoComponent implements OnInit {
     })
   }
 
-  onDeleteServe = (version: number) => {
-    this.modelServeService.deleteServe(this.modelName, version).subscribe({
+  onDeleteServe = (id: number, version: number) => {
+    this.modelServeService.deleteServe(id, this.modelName, version).subscribe({
       next: (result) => {
         this.nzMessageService.success(`The model serve with name: 
${this.modelName} and version: ${version} is deleted.`)
+        // refresh model version status after deleted serve
+        this.fetchModelAllVersions();
       },
       error: (msg) => {
         this.nzMessageService.error(`${msg}, please try again`, {
diff --git 
a/submarine-workbench/workbench-web/src/app/services/model-serve.service.ts 
b/submarine-workbench/workbench-web/src/app/services/model-serve.service.ts
index 4662dd12..b37cc906 100644
--- a/submarine-workbench/workbench-web/src/app/services/model-serve.service.ts
+++ b/submarine-workbench/workbench-web/src/app/services/model-serve.service.ts
@@ -38,9 +38,10 @@ export class ModelServeService {
     this.emitInfoSource.next(id);
   }
 
-  createServe(modelName: string, modelVersion: number) : Observable<string> {
+  createServe(id: number, modelName: string, modelVersion: number) : 
Observable<string> {
     const apiUrl = this.baseApi.getRestApi('/v1/serve');
     const serveSpec : ServeSpec = {
+      id,
       modelName,
       modelVersion
     }
@@ -64,9 +65,10 @@ export class ModelServeService {
     );
   }
 
-  deleteServe(modelName: string, modelVersion: number) : Observable<string> {
+  deleteServe(id: number, modelName: string, modelVersion: number) : 
Observable<string> {
     const apiUrl = this.baseApi.getRestApi(`/v1/serve`);
     const serveSpec : ServeSpec = {
+      id,
       modelName,
       modelVersion
     }
@@ -96,4 +98,4 @@ export class ModelServeService {
     )
   }
 
-}
\ No newline at end of file
+}


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

Reply via email to