You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by fe...@apache.org on 2016/09/01 06:56:45 UTC
zeppelin git commit: [ZEPPELIN-699] Add new synchronous paragraph run
REST API
Repository: zeppelin
Updated Branches:
refs/heads/master 9dc9c7512 -> 7e2a1b5d4
[ZEPPELIN-699] Add new synchronous paragraph run REST API
### What is this PR for?
Right now, when calling the REST API `http://<ip>:<port>/api/notebook/job/<note_id>/<paragraph_id>` Zeppelin always returns **OK** as shown by this source code: https://github.com/apache/incubator-zeppelin/blob/master/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java#L477
This ticket will update the behavior so that Zeppelin also return the result of the paragraph execution
### What type of PR is it?
[Improvement]
### Todos
* [ ] - Code Review
* [ ] - Simple Test
### Is there a relevant Jira issue?
**[ZEPPELIN-699]**
### How should this be tested?
* `git fetch origin pull/746/head:ParagraphExecutionRESTAPI`
* `git checkout ParagraphExecutionRESTAPI`
* `mvn clean package -DskipTests`
* `bin/zeppelin-daemon.sh restart`
* Create a new note
* In the first paragraph, put the following code
```scala
%sh
echo "Current time = "`date +"%T"
```
* Retrieve the current note id in the URL
* Retrieve the current paragraph id
* Use a REST Client like **[POSTman]** to create a HTTP POST query `http://<ip>:<port>/api/notebook/run/<note_id>/<paragraph_id>`
* You should receive something similar as follow for answer
```
{
"status": "OK",
"body": {
"code": "SUCCESS",
"type": "TEXT",
"msg": "Current time = 16:14:18\n"
}
}
```
### Screenshots (if appropriate)
![zeppelin_synchronous_rest_api](https://cloud.githubusercontent.com/assets/1532977/15748069/b4a26a46-28dd-11e6-8f51-aa13ddba3f1c.gif)
API Documentation update
**Existing asynchronous API**
![image](https://cloud.githubusercontent.com/assets/1532977/15773274/5b508cae-2976-11e6-9e52-14d8b7e7828e.png)
**New synchronous API**
![image](https://cloud.githubusercontent.com/assets/1532977/15773309/84965a94-2976-11e6-9719-81d8b555c3c4.png)
### Questions:
* Does the licenses files need update? --> **No**
* Is there breaking changes for older versions? --> **No**
* Does this needs documentation? --> **Yes**
[ZEPPELIN-699]: https://issues.apache.org/jira/browse/ZEPPELIN-699
[POSTman]: https://www.getpostman.com/
Author: DuyHai DOAN <do...@gmail.com>
Closes #746 from doanduyhai/ZEPPELIN-699 and squashes the following commits:
fb0570c [DuyHai DOAN] [ZEPPELIN-699] Update Notebook REST API documentation
8367acf [DuyHai DOAN] [ZEPPELIN-699] Add new synchronous paragraph run REST API
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/7e2a1b5d
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/7e2a1b5d
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/7e2a1b5d
Branch: refs/heads/master
Commit: 7e2a1b5d4f134489428a24c7c5502f189224b506
Parents: 9dc9c75
Author: DuyHai DOAN <do...@gmail.com>
Authored: Thu Jun 2 16:24:38 2016 +0200
Committer: Felix Cheung <fe...@apache.org>
Committed: Wed Aug 31 23:56:39 2016 -0700
----------------------------------------------------------------------
docs/rest-api/rest-notebook.md | 55 ++++++++++++-
.../apache/zeppelin/rest/NotebookRestApi.java | 81 ++++++++++++++++----
.../java/org/apache/zeppelin/notebook/Note.java | 26 ++++++-
3 files changed, 145 insertions(+), 17 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7e2a1b5d/docs/rest-api/rest-notebook.md
----------------------------------------------------------------------
diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md
index c7e17ea..ed39a4f 100644
--- a/docs/rest-api/rest-notebook.md
+++ b/docs/rest-api/rest-notebook.md
@@ -450,12 +450,12 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
</table>
<br/>
-### Run a paragraph
+### Run a paragraph asynchronously
<table class="table-configuration">
<col width="200">
<tr>
<td>Description</td>
- <td>This ```POST``` method runs the paragraph by given notebook and paragraph id.
+ <td>This ```POST``` method runs the paragraph asynchronously by given notebook and paragraph id. This API always return SUCCESS even if the execution of the paragraph fails later because the API is asynchronous
</td>
</tr>
<tr>
@@ -488,6 +488,56 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
</table>
<br/>
+### Run a paragraph synchronously
+ <table class="table-configuration">
+ <col width="200">
+ <tr>
+ <td>Description</td>
+ <td> This ```POST``` method runs the paragraph synchronously by given notebook and paragraph id. This API can return SUCCESS or ERROR depending on the outcome of the paragraph execution
+ </td>
+ </tr>
+ <tr>
+ <td>URL</td>
+ <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/job/[notebookId]/[paragraphId]```</td>
+ </tr>
+ <tr>
+ <td>Success code</td>
+ <td>200</td>
+ </tr>
+ <tr>
+ <td> Fail code</td>
+ <td> 500 </td>
+ </tr>
+ <tr>
+ <td> sample JSON input (optional, only needed when if you want to update dynamic form's value) </td>
+ <td><pre>
+{
+ "name": "name of new notebook",
+ "params": {
+ "formLabel1": "value1",
+ "formLabel2": "value2"
+ }
+}</pre></td>
+ </tr>
+ <tr>
+ <td> sample JSON response </td>
+ <td><pre>{"status": "OK"}</pre></td>
+ </tr>
+ <tr>
+ <td> sample JSON error </td>
+ <td><pre>
+{
+ "status": "INTERNAL\_SERVER\_ERROR",
+ "body": {
+ "code": "ERROR",
+ "type": "TEXT",
+ "msg": "bash: -c: line 0: unexpected EOF while looking for matching ``'\nbash: -c: line 1: syntax error: unexpected end of file\nExitValue: 2"
+ }
+}</pre></td>
+ </tr>
+ </table>
+
+<br/>
### Stop a paragraph
<table class="table-configuration">
<col width="200">
@@ -922,4 +972,3 @@ If you work with Apache Zeppelin and find a need for an additional REST API, ple
</tr>
</tr>
</table>
-
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7e2a1b5d/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
index db0cbec..17ec74f 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/NotebookRestApi.java
@@ -570,7 +570,7 @@ public class NotebookRestApi {
}
/**
- * Run paragraph job REST API
+ * Run asynchronously paragraph job REST API
*
* @param message - JSON with params if user wants to update dynamic form's value
* null, empty string, empty json if user doesn't want to update
@@ -583,7 +583,7 @@ public class NotebookRestApi {
public Response runParagraph(@PathParam("notebookId") String notebookId,
@PathParam("paragraphId") String paragraphId, String message)
throws IOException, IllegalArgumentException {
- LOG.info("run paragraph job {} {} {}", notebookId, paragraphId, message);
+ LOG.info("run paragraph job asynchronously {} {} {}", notebookId, paragraphId, message);
Note note = notebook.getNote(notebookId);
if (note == null) {
@@ -596,22 +596,60 @@ public class NotebookRestApi {
}
// handle params if presented
- if (!StringUtils.isEmpty(message)) {
- RunParagraphWithParametersRequest request =
- gson.fromJson(message, RunParagraphWithParametersRequest.class);
- Map<String, Object> paramsForUpdating = request.getParams();
- if (paramsForUpdating != null) {
- paragraph.settings.getParams().putAll(paramsForUpdating);
- AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
- note.setLastReplName(paragraph.getId());
- note.persist(subject);
- }
- }
+ handleParagraphParams(message, note, paragraph);
note.run(paragraph.getId());
return new JsonResponse<>(Status.OK).build();
}
+/**
+ * Run synchronously a paragraph REST API
+ *
+ * @param noteId - noteId
+ * @param paragraphId - paragraphId
+ * @param message - JSON with params if user wants to update dynamic form's value
+ * null, empty string, empty json if user doesn't want to update
+ *
+ * @return JSON with status.OK
+ * @throws IOException, IllegalArgumentException
+ */
+ @POST
+ @Path("run/{notebookId}/{paragraphId}")
+ @ZeppelinApi
+ public Response runParagraphSynchronously(@PathParam("notebookId") String noteId,
+ @PathParam("paragraphId") String paragraphId,
+ String message) throws
+ IOException, IllegalArgumentException {
+ LOG.info("run paragraph synchronously {} {} {}", noteId, paragraphId, message);
+
+ Note note = notebook.getNote(noteId);
+ if (note == null) {
+ return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build();
+ }
+
+ Paragraph paragraph = note.getParagraph(paragraphId);
+ if (paragraph == null) {
+ return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build();
+ }
+
+ // handle params if presented
+ handleParagraphParams(message, note, paragraph);
+
+ if (paragraph.getListener() == null) {
+ note.initializeJobListenerForParagraph(paragraph);
+ }
+
+ paragraph.run();
+
+ final InterpreterResult result = paragraph.getResult();
+
+ if (result.code() == InterpreterResult.Code.SUCCESS) {
+ return new JsonResponse<>(Status.OK, result).build();
+ } else {
+ return new JsonResponse<>(Status.INTERNAL_SERVER_ERROR, result).build();
+ }
+ }
+
/**
* Stop(delete) paragraph job REST API
*
@@ -803,4 +841,21 @@ public class NotebookRestApi {
return new JsonResponse<>(Status.OK, notebooksFound).build();
}
+
+ private void handleParagraphParams(String message, Note note, Paragraph paragraph)
+ throws IOException {
+ // handle params if presented
+ if (!StringUtils.isEmpty(message)) {
+ RunParagraphWithParametersRequest request =
+ gson.fromJson(message, RunParagraphWithParametersRequest.class);
+ Map<String, Object> paramsForUpdating = request.getParams();
+ if (paramsForUpdating != null) {
+ paragraph.settings.getParams().putAll(paramsForUpdating);
+ AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
+ note.setLastReplName(paragraph.getId());
+ note.persist(subject);
+ }
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/7e2a1b5d/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
index 2b89524..0b2b4c6 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/Note.java
@@ -17,6 +17,8 @@
package org.apache.zeppelin.notebook;
+import static java.lang.String.format;
+
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
@@ -168,6 +170,28 @@ public class Note implements Serializable, ParagraphJobListener {
}
}
+ public void initializeJobListenerForParagraph(Paragraph paragraph) {
+ final Note paragraphNote = paragraph.getNote();
+ if (paragraphNote.getId().equals(this.getId())) {
+ throw new IllegalArgumentException(format("The paragraph %s from note %s " +
+ "does not belong to note %s", paragraph.getId(), paragraphNote.getId(),
+ this.getId()));
+ }
+
+ boolean foundParagraph = false;
+ for (Paragraph ownParagraph : paragraphs) {
+ if (paragraph.getId().equals(ownParagraph.getId())) {
+ paragraph.setListener(this.jobListenerFactory.getParagraphJobListener(this));
+ foundParagraph = true;
+ }
+ }
+
+ if (!foundParagraph) {
+ throw new IllegalArgumentException(format("Cannot find paragraph %s " +
+ "from note %s", paragraph.getId(), paragraphNote.getId()));
+ }
+ }
+
void setJobListenerFactory(JobListenerFactory jobListenerFactory) {
this.jobListenerFactory = jobListenerFactory;
}
@@ -480,7 +504,7 @@ public class Note implements Serializable, ParagraphJobListener {
logger.debug("New paragraph: {}", pText);
p.setEffectiveText(pText);
} else {
- String intpExceptionMsg = String.format("%s",
+ String intpExceptionMsg = format("%s",
p.getJobName()
+ "'s Interpreter "
+ requiredReplName + " not found"