This is an automated email from the ASF dual-hosted git repository.
jiwq 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 d65ed1a SUBMARINE-586. Add the get/delete api & tests.
d65ed1a is described below
commit d65ed1a89ef77b7d0ff05ed674c4bf122a635c55
Author: Ryan Lo <[email protected]>
AuthorDate: Sat Aug 22 14:07:43 2020 +0800
SUBMARINE-586. Add the get/delete api & tests.
### What is this PR for?
User can get/delete notebook instances with REST API
GET request to the URI (/api/v1/notebook/{id}) could get detailed info
about the notebook with the notebook ID.
DELETE request to the URI (/api/v1/notebook/{id}) could delete the notebook
instance with the notebook ID
### What type of PR is it?
[Feature]
### Todos
* [ ] - Task
### What is the Jira issue?
[SUBMARINE-586](https://issues.apache.org/jira/browse/SUBMARINE-586)
### How should this be tested?
[travis
CI](https://travis-ci.org/github/lowc1012/submarine/builds/719366355)
### Screenshots (if appropriate)
<img width="583" alt="螢幕快照 2020-08-22 下午2 11 39"
src="https://user-images.githubusercontent.com/52355146/90950143-8a465280-e481-11ea-82bf-d81097bd57c5.png">
### Questions:
* Does the licenses files need update? No
* Is there breaking changes for older versions? No
* Does this needs documentation? No
Author: Ryan Lo <[email protected]>
Closes #384 from lowc1012/SUBMARINE-586 and squashes the following commits:
c2d00e8 [Ryan Lo] SUBMARINE-586. Update json response
387a62f [Ryan Lo] SUBMARINE-586. Add the get/delete api & tests.
---
helm-charts/submarine/templates/rbac.yaml | 106 +++++----
.../manifests/submarine-cluster/rbac.yaml | 1 +
.../submarine/server/api/notebook/Notebook.java | 26 ++
.../submarine/server/notebook/NotebookManager.java | 50 +++-
.../submarine/server/rest/NotebookRestApi.java | 16 +-
.../src/test/resources/environment/test_env_3.json | 10 +
.../server/submitter/k8s/K8sSubmitter.java | 40 +++-
.../src/test/resources/notebook_req.json | 2 +-
.../apache/submarine/rest/NotebookRestApiIT.java | 261 +++++++++++++++++++++
.../src/test/resources/notebook/notebook-req.json | 15 ++
.../src/test/resources/notebook/notebook-req.yaml | 10 +
11 files changed, 464 insertions(+), 73 deletions(-)
diff --git a/helm-charts/submarine/templates/rbac.yaml
b/helm-charts/submarine/templates/rbac.yaml
index fe0f5a5..c108122 100644
--- a/helm-charts/submarine/templates/rbac.yaml
+++ b/helm-charts/submarine/templates/rbac.yaml
@@ -1,52 +1,54 @@
-#
-# 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.
-#
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
- name: "{{ .Values.submarine.server.name }}"
-rules:
-- apiGroups:
- - kubeflow.org
- resources:
- - tfjobs
- - tfjobs/status
- - pytorchjobs
- - pytorchjobs/status
- verbs:
- - get
- - list
- - watch
- - create
- - delete
- - deletecollection
- - patch
- - update
-
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
- name: "{{ .Values.submarine.server.name }}"
-subjects:
-- kind: ServiceAccount
- namespace: {{ .Release.Namespace }}
- name: "{{ .Values.submarine.server.name }}"
-roleRef:
- kind: ClusterRole
- name: "{{ .Values.submarine.server.name }}"
- apiGroup: ""
+#
+# 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.
+#
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: "{{ .Values.submarine.server.name }}"
+rules:
+- apiGroups:
+ - kubeflow.org
+ resources:
+ - tfjobs
+ - tfjobs/status
+ - pytorchjobs
+ - pytorchjobs/status
+ - notebooks
+ - notebooks/status
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - delete
+ - deletecollection
+ - patch
+ - update
+
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: "{{ .Values.submarine.server.name }}"
+subjects:
+- kind: ServiceAccount
+ namespace: {{ .Release.Namespace }}
+ name: "{{ .Values.submarine.server.name }}"
+roleRef:
+ kind: ClusterRole
+ name: "{{ .Values.submarine.server.name }}"
+ apiGroup: ""
diff --git a/submarine-cloud/manifests/submarine-cluster/rbac.yaml
b/submarine-cloud/manifests/submarine-cluster/rbac.yaml
index a8ecd37..6687a81 100644
--- a/submarine-cloud/manifests/submarine-cluster/rbac.yaml
+++ b/submarine-cloud/manifests/submarine-cluster/rbac.yaml
@@ -33,6 +33,7 @@ items:
resources:
- tfjobs
- pytorchjobs
+ - notebooks
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
diff --git
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/notebook/Notebook.java
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/notebook/Notebook.java
index b71aded..1e0ea36 100644
---
a/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/notebook/Notebook.java
+++
b/submarine-server/server-api/src/main/java/org/apache/submarine/server/api/notebook/Notebook.java
@@ -117,4 +117,30 @@ public class Notebook {
}
}
+ public void rebuild(Notebook notebook) {
+ if (notebook != null) {
+ if (notebook.getName() != null) {
+ this.setName(notebook.getName());
+ }
+ if (notebook.getUid() != null) {
+ this.setUid(notebook.getUid());
+ }
+ if (notebook.getUrl() != null) {
+ this.setUrl(notebook.getUrl());
+ }
+ if (notebook.getSpec() != null) {
+ this.setSpec(notebook.getSpec());
+ }
+ if (notebook.getStatus() != null) {
+ this.setStatus(notebook.getStatus());
+ }
+ if (notebook.getCreatedTime() != null) {
+ this.setCreatedTime(notebook.getCreatedTime());
+ }
+ if (notebook.getDeletedTime() != null) {
+ this.setDeletedTime(notebook.getDeletedTime());
+ }
+ }
+ }
+
}
diff --git
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/notebook/NotebookManager.java
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/notebook/NotebookManager.java
index 5547c23..1e78022 100644
---
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/notebook/NotebookManager.java
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/notebook/NotebookManager.java
@@ -23,12 +23,16 @@ import
org.apache.submarine.commons.utils.exception.SubmarineRuntimeException;
import org.apache.submarine.server.SubmarineServer;
import org.apache.submarine.server.SubmitterManager;
import org.apache.submarine.server.api.Submitter;
+import org.apache.submarine.server.api.environment.Environment;
import org.apache.submarine.server.api.notebook.Notebook;
import org.apache.submarine.server.api.notebook.NotebookId;
import org.apache.submarine.server.api.spec.NotebookSpec;
+import org.apache.submarine.server.environment.EnvironmentManager;
import javax.ws.rs.core.Response;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -78,19 +82,34 @@ public class NotebookManager {
Notebook notebook = submitter.createNotebook(spec);
notebook.setNotebookId(generateNotebookId());
notebook.setSpec(spec);
+ NotebookSpec notebookSpec = notebook.getSpec();
+ EnvironmentManager environmentManager = EnvironmentManager.getInstance();
+ Environment environment =
environmentManager.getEnvironment(spec.getEnvironment().getName());
+ if (environment.getEnvironmentSpec() != null) {
+ notebookSpec.setEnvironment(environment.getEnvironmentSpec());
+ }
cachedNotebookMap.putIfAbsent(notebook.getNotebookId().toString(),
notebook);
return notebook;
}
/**
* List notebook instances
- * @param status status, if null will return all notebooks
+ * @param namespace namespace, if null will return all notebooks
* @return list
* @throws SubmarineRuntimeException the service error
*/
- public List<Notebook> listNotebooksByStatus(String status) throws
SubmarineRuntimeException {
- //TODO(ryan): implement the method
- return null;
+ public List<Notebook> listNotebooksByNamespace(String namespace) throws
SubmarineRuntimeException {
+ List<Notebook> notebookList = new ArrayList<>();
+ for (Map.Entry<String, Notebook> entry : cachedNotebookMap.entrySet()) {
+ Notebook notebook = entry.getValue();
+ Notebook patchNotebook = submitter.findNotebook(notebook.getSpec());
+ if (namespace == null || namespace.length() == 0
+ ||
namespace.toLowerCase().equals(patchNotebook.getSpec().getMeta().getNamespace()))
{
+ notebook.rebuild(patchNotebook);
+ notebookList.add(notebook);
+ }
+ }
+ return notebookList;
}
/**
@@ -100,8 +119,12 @@ public class NotebookManager {
* @throws SubmarineRuntimeException the service error
*/
public Notebook getNotebook(String id) throws SubmarineRuntimeException {
- //TODO(ryan): implement the method
- return null;
+ checkNotebookId(id);
+ Notebook notebook = cachedNotebookMap.get(id);
+ NotebookSpec spec = notebook.getSpec();
+ Notebook patchNotebook = submitter.findNotebook(spec);
+ notebook.rebuild(patchNotebook);
+ return notebook;
}
/**
@@ -111,8 +134,12 @@ public class NotebookManager {
* @throws SubmarineRuntimeException the service error
*/
public Notebook deleteNotebook(String id) throws SubmarineRuntimeException {
- //TODO(ryan): implement the method
- return null;
+ checkNotebookId(id);
+ Notebook notebook = cachedNotebookMap.remove(id);
+ NotebookSpec spec = notebook.getSpec();
+ Notebook patchNotebook = submitter.deleteNotebook(spec);
+ notebook.rebuild(patchNotebook);
+ return notebook;
}
/**
@@ -136,4 +163,11 @@ public class NotebookManager {
}
}
+ private void checkNotebookId(String id) throws SubmarineRuntimeException {
+ NotebookId notebookId = NotebookId.fromString(id);
+ if (notebookId == null || !cachedNotebookMap.containsKey(id)) {
+ throw new
SubmarineRuntimeException(Response.Status.NOT_FOUND.getStatusCode(),
+ "Not found notebook server.");
+ }
+ }
}
diff --git
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/NotebookRestApi.java
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/NotebookRestApi.java
index ac61cb2..bfa4c91 100644
---
a/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/NotebookRestApi.java
+++
b/submarine-server/server-core/src/main/java/org/apache/submarine/server/rest/NotebookRestApi.java
@@ -43,7 +43,7 @@ import javax.ws.rs.core.Response;
import java.util.List;
/**
- * Notebook Service REST API v1
+ * Notebook REST API v1. It can accept {@link NotebookSpec} to create a
notebook server.
*/
@Path(RestConstants.V1 + "/" + RestConstants.NOTEBOOK)
@Produces({MediaType.APPLICATION_JSON + "; " + RestConstants.CHARSET_UTF8})
@@ -94,8 +94,8 @@ public class NotebookRestApi {
}
/**
- * List all notebooks created by the user
- * @param status status
+ * List all notebooks
+ * @param namespace namespace
* @return notebook list
*/
@GET
@@ -105,11 +105,11 @@ public class NotebookRestApi {
responses = {
@ApiResponse(description = "successful operation", content =
@Content(
schema = @Schema(implementation =
JsonResponse.class)))})
- public Response listNotebooks(@QueryParam("status") String status) {
+ public Response listNotebooks(@QueryParam("namespace") String namespace) {
try {
- List<Notebook> notebookList =
notebookManager.listNotebooksByStatus(status);
+ List<Notebook> notebookList =
notebookManager.listNotebooksByNamespace(namespace);
return new
JsonResponse.Builder<List<Notebook>>(Response.Status.OK).success(true)
- .result(notebookList).build();
+ .message("List all notebook
instances").result(notebookList).build();
} catch (SubmarineRuntimeException e) {
return parseNotebookServiceException(e);
}
@@ -134,7 +134,7 @@ public class NotebookRestApi {
try {
Notebook notebook = notebookManager.getNotebook(id);
return new
JsonResponse.Builder<Notebook>(Response.Status.OK).success(true)
- .result(notebook).build();
+ .message("Get the notebook instance").result(notebook).build();
} catch (SubmarineRuntimeException e) {
return parseNotebookServiceException(e);
}
@@ -159,7 +159,7 @@ public class NotebookRestApi {
try {
Notebook notebook = notebookManager.deleteNotebook(id);
return new
JsonResponse.Builder<Notebook>(Response.Status.OK).success(true)
- .result(notebook).build();
+ .message("Delete the notebook
instance").result(notebook).build();
} catch (SubmarineRuntimeException e) {
return parseNotebookServiceException(e);
}
diff --git
a/submarine-server/server-core/src/test/resources/environment/test_env_3.json
b/submarine-server/server-core/src/test/resources/environment/test_env_3.json
new file mode 100644
index 0000000..0e4469a
--- /dev/null
+++
b/submarine-server/server-core/src/test/resources/environment/test_env_3.json
@@ -0,0 +1,10 @@
+{
+ "name": "my-submarine-env",
+ "dockerImage" : "apache/submarine:jupyter-notebook-0.5.0-SNAPSHOT",
+ "kernelSpec" : {
+ "name" : "team_default_python_3.7",
+ "channels" : ["defaults"],
+ "dependencies" :
+ ["_ipyw_jlab_nb_ext_conf=0.1.0=py37_0"]
+ }
+}
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 5fe17ad..cdc33f0 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
@@ -21,6 +21,8 @@ package org.apache.submarine.server.submitter.k8s;
import java.io.FileReader;
import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
@@ -30,6 +32,7 @@ import io.kubernetes.client.Configuration;
import io.kubernetes.client.JSON;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.apis.CustomObjectsApi;
+import io.kubernetes.client.models.V1DeleteOptionsBuilder;
import io.kubernetes.client.models.V1Pod;
import io.kubernetes.client.models.V1PodList;
import io.kubernetes.client.models.V1Status;
@@ -252,14 +255,34 @@ public class K8sSubmitter implements Submitter {
@Override
public Notebook findNotebook(NotebookSpec spec) throws
SubmarineRuntimeException {
- // TODO(ryan): Implement this method
- return null;
+ Notebook notebook;
+ try {
+ NotebookCR notebookCR = NotebookSpecParser.parseNotebook(spec);
+ Object object = api.getNamespacedCustomObject(notebookCR.getGroup(),
notebookCR.getVersion(),
+ notebookCR.getMetadata().getNamespace(),
+ notebookCR.getPlural(), notebookCR.getMetadata().getName());
+ notebook = parseResponseObject(object);
+ } catch (ApiException e) {
+ throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+ }
+ return notebook;
}
@Override
public Notebook deleteNotebook(NotebookSpec spec) throws
SubmarineRuntimeException {
- // TODO(ryan): Implement this method
- return null;
+ Notebook notebook;
+ try {
+ NotebookCR notebookCR = NotebookSpecParser.parseNotebook(spec);
+ Object object = api.deleteNamespacedCustomObject(notebookCR.getGroup(),
notebookCR.getVersion(),
+ notebookCR.getMetadata().getNamespace(), notebookCR.getPlural(),
+ notebookCR.getMetadata().getName(),
+ new
V1DeleteOptionsBuilder().withApiVersion(notebookCR.getApiVersion()).build(),
+ null, null, null);
+ notebook = parseResponseObject(object);
+ } catch (ApiException e) {
+ throw new SubmarineRuntimeException(e.getCode(), e.getMessage());
+ }
+ return notebook;
}
private Notebook parseResponseObject(Object obj) throws
SubmarineRuntimeException {
@@ -280,6 +303,15 @@ public class K8sSubmitter implements Submitter {
notebook.setCreatedTime(createdTime.toString());
notebook.setStatus(Notebook.Status.STATUS_CREATED.getValue());
}
+
+ // deleted notebook
+ if (notebookCR.getMetadata().getName() == null) {
+ SimpleDateFormat dateFormat = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+ Date current = new Date();
+ notebook.setDeletedTime(dateFormat.format(current));
+ notebook.setStatus(Notebook.Status.STATUS_DELETED.toString());
+ }
+
} catch (JsonSyntaxException e) {
LOG.error("K8s submitter: parse response object failed by " +
e.getMessage(), e);
throw new SubmarineRuntimeException(500, "K8s Submitter parse upstream
response failed.");
diff --git
a/submarine-server/server-submitter/submitter-k8s/src/test/resources/notebook_req.json
b/submarine-server/server-submitter/submitter-k8s/src/test/resources/notebook_req.json
index e1dace6..e46aec0 100644
---
a/submarine-server/server-submitter/submitter-k8s/src/test/resources/notebook_req.json
+++
b/submarine-server/server-submitter/submitter-k8s/src/test/resources/notebook_req.json
@@ -4,7 +4,7 @@
"namespace": "default"
},
"environment": {
- "image": "apache/submarine:tf2.1.0-jupyter"
+ "name": "my-submarine-env"
},
"spec": {
"envVars": {
diff --git
a/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
new file mode 100644
index 0000000..1359ed8
--- /dev/null
+++
b/submarine-test/test-k8s/src/test/java/org/apache/submarine/rest/NotebookRestApiIT.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.submarine.rest;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.kubernetes.client.ApiClient;
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.Configuration;
+import io.kubernetes.client.JSON;
+import io.kubernetes.client.apis.CustomObjectsApi;
+import io.kubernetes.client.util.ClientBuilder;
+import io.kubernetes.client.util.KubeConfig;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.submarine.server.AbstractSubmarineServerTest;
+import org.apache.submarine.server.api.environment.Environment;
+import org.apache.submarine.server.api.experiment.Experiment;
+import org.apache.submarine.server.api.notebook.Notebook;
+import org.apache.submarine.server.api.notebook.NotebookId;
+import org.apache.submarine.server.gson.NotebookIdDeserializer;
+import org.apache.submarine.server.gson.NotebookIdSerializer;
+import org.apache.submarine.server.response.JsonResponse;
+import org.apache.submarine.server.rest.RestConstants;
+import org.joda.time.DateTime;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Date;
+
+@SuppressWarnings("rawtypes")
+public class NotebookRestApiIT extends AbstractSubmarineServerTest {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(NotebookRestApiIT.class);
+
+ private static CustomObjectsApi k8sApi;
+ private static final String BASE_API_PATH = "/api/" + RestConstants.V1 + "/"
+ RestConstants.NOTEBOOK;
+ public static final String VERSION = "v1";
+ public static final String GROUP = "kubeflow.org";
+ public static final String PLURAL = "notebooks";
+
+ private final Gson gson = new GsonBuilder()
+ .registerTypeAdapter(NotebookId.class, new NotebookIdSerializer())
+ .registerTypeAdapter(NotebookId.class, new NotebookIdDeserializer())
+ .create();
+
+ @BeforeClass
+ public static void startUp() throws IOException {
+ Assert.assertTrue(checkIfServerIsRunning());
+
+ // The kube config path defined by kind-cluster-build.sh
+ String confPath = System.getProperty("user.home") +
"/.kube/kind-config-kind";
+ KubeConfig config = KubeConfig.loadKubeConfig(new FileReader(confPath));
+ ApiClient client = ClientBuilder.kubeconfig(config).build();
+ Configuration.setDefaultApiClient(client);
+ k8sApi = new CustomObjectsApi();
+ }
+
+ @Test
+ public void testServerPing() throws IOException {
+ GetMethod response = httpGet(BASE_API_PATH + "/" + RestConstants.PING);
+ String requestBody = response.getResponseBodyAsString();
+ Gson gson = new Gson();
+ JsonResponse jsonResponse = gson.fromJson(requestBody, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
jsonResponse.getCode());
+ Assert.assertEquals("Pong", jsonResponse.getResult().toString());
+ }
+
+ @Test
+ public void testCreateNotebookWithJsonSpec() throws Exception {
+ // create environment
+ String envBody = loadContent("environment/test_env_3.json");
+ run(envBody, "application/json");
+
+ Gson gson = new GsonBuilder().create();
+ GetMethod getMethod = httpGet(ENV_PATH + "/" + ENV_NAME);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ getMethod.getStatusCode());
+
+ String json = getMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ jsonResponse.getCode());
+
+ Environment getEnvironment =
+ gson.fromJson(gson.toJson(jsonResponse.getResult()),
Environment.class);
+ Assert.assertEquals(ENV_NAME,
getEnvironment.getEnvironmentSpec().getName());
+
+ String body = loadContent("notebook/notebook-req.json");
+ runTest(body, "application/json");
+
+ deleteEnvironment();
+ }
+
+ @Test
+ public void testCreateNotebookWithYamlSpec() throws Exception {
+ // create environment
+ String envBody = loadContent("environment/test_env_3.json");
+ run(envBody, "application/json");
+
+ Gson gson = new GsonBuilder().create();
+ GetMethod getMethod = httpGet(ENV_PATH + "/" + ENV_NAME);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ getMethod.getStatusCode());
+
+ String json = getMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
+ jsonResponse.getCode());
+
+ Environment getEnvironment =
+ gson.fromJson(gson.toJson(jsonResponse.getResult()),
Environment.class);
+ Assert.assertEquals(ENV_NAME,
getEnvironment.getEnvironmentSpec().getName());
+
+ String body = loadContent("notebook/notebook-req.yaml");
+ runTest(body, "application/yaml");
+
+ deleteEnvironment();
+ }
+
+ @Test
+ public void testCreateNotebookWithInvalidSpec() throws Exception {
+ PostMethod postMethod = httpPost(BASE_API_PATH, "",
MediaType.APPLICATION_JSON);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
postMethod.getStatusCode());
+
+ String json = postMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
jsonResponse.getCode());
+ }
+
+ private void runTest(String body, String contentType) throws Exception {
+ // create
+ LOG.info("Create a notebook server by Notebook REST API");
+ PostMethod postMethod = httpPost(BASE_API_PATH, body, contentType);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
postMethod.getStatusCode());
+
+ String json = postMethod.getResponseBodyAsString();
+ JsonResponse jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
jsonResponse.getCode());
+
+ Notebook createdNotebook =
gson.fromJson(gson.toJson(jsonResponse.getResult()), Notebook.class);
+ verifyCreateNotebookApiResult(createdNotebook);
+
+ // find
+ GetMethod getMethod = httpGet(BASE_API_PATH + "/" +
createdNotebook.getNotebookId().toString());
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
getMethod.getStatusCode());
+
+ json = getMethod.getResponseBodyAsString();
+ jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
jsonResponse.getCode());
+
+ Notebook foundNotebook =
gson.fromJson(gson.toJson(jsonResponse.getResult()), Notebook.class);
+ verifyGetNotebookApiResult(createdNotebook, foundNotebook);
+
+ // delete
+ DeleteMethod deleteMethod =
+ httpDelete(BASE_API_PATH + "/" +
createdNotebook.getNotebookId().toString());
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
deleteMethod.getStatusCode());
+
+ json = deleteMethod.getResponseBodyAsString();
+ jsonResponse = gson.fromJson(json, JsonResponse.class);
+ Assert.assertEquals(Response.Status.OK.getStatusCode(),
jsonResponse.getCode());
+
+ Notebook deletedNotebook =
gson.fromJson(gson.toJson(jsonResponse.getResult()), Notebook.class);
+ verifyDeleteNotebookApiResult(createdNotebook, deletedNotebook);
+ }
+
+ private void verifyCreateNotebookApiResult(Notebook createdNotebook) {
+ Assert.assertNotNull(createdNotebook.getUid());
+ Assert.assertNotNull(createdNotebook.getCreatedTime());
+ Assert.assertEquals(Experiment.Status.STATUS_CREATED.getValue(),
createdNotebook.getStatus());
+ }
+
+ private void verifyGetNotebookApiResult(Notebook createdNotebook,
+ Notebook foundNotebook) throws
Exception {
+ Assert.assertEquals(createdNotebook.getNotebookId(),
foundNotebook.getNotebookId());
+ Assert.assertEquals(createdNotebook.getUid(), foundNotebook.getUid());
+ Assert.assertEquals(createdNotebook.getCreatedTime(),
foundNotebook.getCreatedTime());
+ Assert.assertEquals(createdNotebook.getName(), foundNotebook.getName());
+ assertGetK8sResult(foundNotebook);
+ }
+
+ private void verifyDeleteNotebookApiResult(Notebook createdNotebook,
+ Notebook deletedNotebook) {
+ Assert.assertEquals(createdNotebook.getName(), deletedNotebook.getName());
+ Assert.assertEquals(Notebook.Status.STATUS_DELETED.getValue(),
deletedNotebook.getStatus());
+ assertDeleteK8sResult(deletedNotebook);
+ }
+
+ private void assertGetK8sResult(Notebook notebook) throws Exception {
+ JsonObject rootObject = getNotebookByK8sApi(GROUP, VERSION,
notebook.getSpec().getMeta().getNamespace(),
+ PLURAL, notebook.getName());
+
+ JsonObject metadataObject = rootObject.getAsJsonObject("metadata");
+ String uid = metadataObject.getAsJsonPrimitive("uid").getAsString();
+ LOG.info("Uid from Notebook REST is {}", notebook.getUid());
+ LOG.info("Uid from K8s REST is {}", uid);
+ Assert.assertEquals(notebook.getUid(), uid);
+
+ JsonArray envVars = (JsonArray) rootObject.getAsJsonObject("spec")
+ .getAsJsonObject("template").getAsJsonObject("spec")
+ .getAsJsonArray("containers").get(0).getAsJsonObject().get("env");
+ Assert.assertNotNull("The environment command not found.", envVars);
+
+ String creationTimestamp =
+
metadataObject.getAsJsonPrimitive("creationTimestamp").getAsString();
+ Date expectedDate = new DateTime(notebook.getCreatedTime()).toDate();
+ Date actualDate = new DateTime(creationTimestamp).toDate();
+ LOG.info("CreationTimestamp from Notebook REST is {}", expectedDate);
+ LOG.info("CreationTimestamp from K8s REST is {}", actualDate);
+ Assert.assertEquals(expectedDate, actualDate);
+ }
+
+ private void assertDeleteK8sResult(Notebook notebook) {
+ JsonObject rootObject = null;
+ try {
+ rootObject = getNotebookByK8sApi(GROUP, VERSION,
notebook.getSpec().getMeta().getNamespace(),
+ PLURAL, notebook.getName());
+ } catch (ApiException e) {
+ Assert.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
e.getCode());
+ } finally {
+ Assert.assertNull(rootObject);
+ }
+ }
+
+ private JsonObject getNotebookByK8sApi(String group, String version, String
namespace, String plural,
+ String name) throws ApiException {
+ Object obj = k8sApi.getNamespacedCustomObject(group, version, namespace,
plural, name);
+ Gson gson = new JSON().getGson();
+ JsonObject rootObject = gson.toJsonTree(obj).getAsJsonObject();
+ Assert.assertNotNull("Parse the K8s API Server response failed.",
rootObject);
+ return rootObject;
+ }
+}
diff --git
a/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.json
b/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.json
new file mode 100644
index 0000000..0b95433
--- /dev/null
+++ b/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.json
@@ -0,0 +1,15 @@
+{
+ "meta": {
+ "name": "test-nb",
+ "namespace": "default"
+ },
+ "environment": {
+ "name": "my-submarine-env"
+ },
+ "spec": {
+ "envVars": {
+ "TEST_ENV": "test"
+ },
+ "resources": "cpu=1,memory=1.0Gi"
+ }
+}
diff --git
a/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.yaml
b/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.yaml
new file mode 100644
index 0000000..824c32e
--- /dev/null
+++ b/submarine-test/test-k8s/src/test/resources/notebook/notebook-req.yaml
@@ -0,0 +1,10 @@
+---
+meta:
+ name: test-nb
+ namespace: default
+environment:
+ name: my-submarine-env
+spec:
+ envVars:
+ TEST_ENV: test
+ resources: cpu=1,memory=1.0Gi
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]