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 <[email protected]>
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.

### 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.

Swagger UI

### 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: [email protected]
For additional commands, e-mail: [email protected]