You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by bz...@apache.org on 2015/12/10 12:35:19 UTC

incubator-zeppelin git commit: Add job operation API

Repository: incubator-zeppelin
Updated Branches:
  refs/heads/master c5c92ed1f -> 664a13a2f


Add job operation API

This PR is related to https://issues.apache.org/jira/browse/ZEPPELIN-137.
I added some notebook job operations.

Here is the examples.
* Get notebook job status.
```
]# curl -XGET http://121.156.59.2:8080/api/notebook/job/2B3Z5BXJA
{"status":"OK","body":[{"id":"20151121-212654_766735423","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:39 KST 2015"},{"id":"20151121-212657_730976687","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:40 KST 2015"},{"id":"20151121-235508_752057578","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:40 KST 2015"},{"id":"20151121-235900_1491052248","status":"FINISHED","finished":"Tue Nov 24 14:21:41 KST 2015","started":"Tue Nov 24 14:21:40 KST 2015"},{"id":"20151121-235909_1520022794","status":"RUNNING","finished":"Tue Nov 24 02:53:51 KST 2015","started":"Tue Nov 24 14:21:41 KST 2015"}]}
```

* Run notebook job.
```
]# curl -XDELETE http://121.156.59.2:8080/api/notebook/job/2B3Z5BXJA
{"status":"ACCEPTED"}
```

* Stop(Delete) notebook job.
```
]# curl -XDELETE http://121.156.59.2:8080/api/notebook/job/2B3Z5BXJA
{"status":"ACCEPTED"}
```

* Run requested paragraph job.
```
]# curl -XPOST http://121.156.59.2:8080/api/notebook/job/2B3Z5BXJA/20151121-212654_766735423
{"status":"ACCEPTED"}
```

* Stop(Delete) requested paragraph job.
```
]# curl -XDELETE http://121.156.59.2:8080/api/notebook/job/2B3Z5BXJA/20151121-212654_766735423
{"status":"ACCEPTED"}
```

Author: astroshim <hs...@nflabs.com>
Author: root <root@ktadm002.(none)>

Closes #465 from astroshim/improve/addApis and squashes the following commits:

c668ed9 [astroshim] update REST API docs
a83289e [astroshim] fix restapi testcase(status code).
d0afd18 [astroshim] fix http status code.
775e7c8 [astroshim] update testcase.
fe8d5eb [astroshim] check if note config null.
dbd2d03 [astroshim] update note.java to find error.
861f652 [astroshim] fix indent error.
2989096 [astroshim] add generateParagraphsInfo method to Note.java
cb99391 [astroshim] merge with master
8002cd5 [astroshim] change restapi testcase to find build problem.
5bb2c5e [astroshim] fix getCronJob response code.
73f3b2b [astroshim] add cron job api's
3e2ea3d [astroshim] update testcase.
722f05b [astroshim] merge seperated job testcase.
2bff6c9 [root] add job run and stop testcase.
6103c42 [astroshim] fix LineLength build error.
e5f7103 [astroshim] fix whitespace build error.
7a7f993 [astroshim] fix indentation build error.
6c1acf3 [astroshim] fix result message.
1255674 [astroshim] add job operation api


Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/664a13a2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/664a13a2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/664a13a2

Branch: refs/heads/master
Commit: 664a13a2f69498fdd4ce4c1a29d970d9fb40f882
Parents: c5c92ed
Author: astroshim <hs...@nflabs.com>
Authored: Thu Dec 10 00:19:49 2015 +0900
Committer: Alexander Bezzubov <bz...@apache.org>
Committed: Thu Dec 10 20:35:03 2015 +0900

----------------------------------------------------------------------
 docs/rest-api/rest-notebook.md                  | 256 +++++++++++++++++++
 .../apache/zeppelin/rest/NotebookRestApi.java   | 196 +++++++++++++-
 .../zeppelin/rest/message/CronRequest.java      |  38 +++
 .../zeppelin/rest/AbstractTestRestApi.java      |   4 +
 .../zeppelin/rest/ZeppelinRestApiTest.java      | 106 +++++++-
 .../java/org/apache/zeppelin/notebook/Note.java |  15 ++
 6 files changed, 613 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/docs/rest-api/rest-notebook.md
----------------------------------------------------------------------
diff --git a/docs/rest-api/rest-notebook.md b/docs/rest-api/rest-notebook.md
index ffee95a..1bb0e53 100644
--- a/docs/rest-api/rest-notebook.md
+++ b/docs/rest-api/rest-notebook.md
@@ -169,3 +169,259 @@ limitations under the License.
     </tr>
   </table>
   
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Run notebook job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method run all paragraph in the given notebook id.
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/job/[notebookId]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Stop notebook job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```DELETE``` method stop all paragraph in the given notebook id. 
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/job/[notebookId]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Get notebook job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method get all paragraph status by the given notebook id. 
+          The body field of the returned JSON contains of the array that compose of the paragraph id, paragraph status, paragraph finish date, paragraph started date.
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/job/[notebookId]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK","body":[{"id":"20151121-212654_766735423","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:39 KST 2015"},{"id":"20151121-212657_730976687","status":"FINISHED","finished":"Tue Nov 24 14:21:40 KST 2015","started":"Tue Nov 24 14:21:40 KST 2015"}]}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Run paragraph job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method run the paragraph by given notebook and paragraph id. 
+      </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 response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Stop paragraph job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```DELETE``` method stop the paragraph by given notebook and paragraph id. 
+      </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 response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Add cron job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```POST``` method add cron job by the given notebook id. 
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/cron/[notebookId]```</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 </td>
+      <td><pre>{"cron": "cron expression of notebook"}</pre></td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Remove cron job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```DELETE``` method remove cron job by the given notebook id. 
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/cron/[notebookId]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK"}</pre></td>
+    </tr>
+  </table>
+  
+<br/>
+
+  <table class="table-configuration">
+    <col width="200">
+    <tr>
+      <th>Get clone job</th>
+      <th></th>
+    </tr>
+    <tr>
+      <td>Description</td>
+      <td>This ```GET``` method get cron job expression of given notebook id. 
+          The body field of the returned JSON contain the cron expression.
+      </td>
+    </tr>
+    <tr>
+      <td>URL</td>
+      <td>```http://[zeppelin-server]:[zeppelin-port]/api/notebook/cron/[notebookId]```</td>
+    </tr>
+    <tr>
+      <td>Success code</td>
+      <td>200</td>
+    </tr>
+    <tr>
+      <td> Fail code</td>
+      <td> 500 </td>
+    </tr>
+    <tr>
+      <td> sample JSON response </td>
+      <td><pre>{"status":"OK","body":"* * * * * ?"}</pre></td>
+    </tr>
+  </table>
+  

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/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 2b98633..b3f912e 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
@@ -29,12 +29,15 @@ import javax.ws.rs.core.Response.Status;
 import org.apache.zeppelin.interpreter.InterpreterSetting;
 import org.apache.zeppelin.notebook.Note;
 import org.apache.zeppelin.notebook.Notebook;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.rest.message.CronRequest;
 import org.apache.zeppelin.rest.message.InterpreterSettingListForNoteBind;
 import org.apache.zeppelin.rest.message.NewInterpreterSettingRequest;
 import org.apache.zeppelin.rest.message.NewNotebookRequest;
 import org.apache.zeppelin.server.JsonResponse;
 import org.apache.zeppelin.server.ZeppelinServer;
 import org.apache.zeppelin.socket.NotebookServer;
+import org.quartz.CronExpression;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -166,11 +169,12 @@ public class NotebookRestApi {
     notebookServer.broadcastNoteList();
     return new JsonResponse(Status.OK, "").build();
   }
+  
   /**
    * Clone note REST API
    * @param
    * @return JSON with status.CREATED
-   * @throws IOException
+   * @throws IOException, CloneNotSupportedException, IllegalArgumentException
    */
   @POST
   @Path("{notebookId}")
@@ -185,4 +189,194 @@ public class NotebookRestApi {
     notebookServer.broadcastNoteList();
     return new JsonResponse(Status.CREATED, "", newNote.getId()).build();
   }
+  
+  /**
+   * Run notebook jobs REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @POST
+  @Path("job/{notebookId}")
+  public Response runNoteJobs(@PathParam("notebookId") String notebookId) throws
+      IOException, IllegalArgumentException {
+    logger.info("run notebook jobs {} ", notebookId);
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+    
+    note.runAll();
+    return new JsonResponse(Status.OK).build();
+  }
+
+  /**
+   * Stop(delete) notebook jobs REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @DELETE
+  @Path("job/{notebookId}")
+  public Response stopNoteJobs(@PathParam("notebookId") String notebookId) throws
+      IOException, IllegalArgumentException {
+    logger.info("stop notebook jobs {} ", notebookId);
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+
+    for (Paragraph p : note.getParagraphs()) {
+      if (!p.isTerminated()) {
+        p.abort();
+      }
+    }
+    return new JsonResponse(Status.OK).build();
+  }
+  
+  /**
+   * Get notebook job status REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @GET
+  @Path("job/{notebookId}")
+  public Response getNoteJobStatus(@PathParam("notebookId") String notebookId) throws
+      IOException, IllegalArgumentException {
+    logger.info("get notebook job status.");
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+
+    return new JsonResponse(Status.OK, null, note.generateParagraphsInfo()).build();
+  }
+  
+  /**
+   * Run paragraph job REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @POST
+  @Path("job/{notebookId}/{paragraphId}")
+  public Response runParagraph(@PathParam("notebookId") String notebookId, 
+                               @PathParam("paragraphId") String paragraphId) throws
+                               IOException, IllegalArgumentException {
+    logger.info("run paragraph job {} {} ", notebookId, paragraphId);
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+    
+    if (note.getParagraph(paragraphId) == null) {
+      return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build();
+    }
+
+    note.run(paragraphId);
+    return new JsonResponse(Status.OK).build();
+  }
+
+  /**
+   * Stop(delete) paragraph job REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @DELETE
+  @Path("job/{notebookId}/{paragraphId}")
+  public Response stopParagraph(@PathParam("notebookId") String notebookId, 
+                                @PathParam("paragraphId") String paragraphId) throws
+                                IOException, IllegalArgumentException {
+    logger.info("stop paragraph job {} ", notebookId);
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+
+    Paragraph p = note.getParagraph(paragraphId);
+    if (p == null) {
+      return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build();
+    }
+    p.abort();
+    return new JsonResponse(Status.OK).build();
+  }
+    
+  /**
+   * Register cron job REST API
+   * @param message - JSON with cron expressions.
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @POST
+  @Path("cron/{notebookId}")
+  public Response registerCronJob(@PathParam("notebookId") String notebookId, String message) throws
+      IOException, IllegalArgumentException {
+    logger.info("Register cron job note={} request cron msg={}", notebookId, message);
+
+    CronRequest request = gson.fromJson(message,
+                          CronRequest.class);
+    
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+    
+    if (!CronExpression.isValidExpression(request.getCronString())) {
+      return new JsonResponse(Status.BAD_REQUEST, "wrong cron expressions.").build();
+    }
+
+    Map<String, Object> config = note.getConfig();
+    config.put("cron", request.getCronString());
+    note.setConfig(config);
+    notebook.refreshCron(note.id());
+    
+    return new JsonResponse(Status.OK).build();
+  }
+  
+  /**
+   * Remove cron job REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @DELETE
+  @Path("cron/{notebookId}")
+  public Response removeCronJob(@PathParam("notebookId") String notebookId) throws
+      IOException, IllegalArgumentException {
+    logger.info("Remove cron job note {}", notebookId);
+
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+    
+    Map<String, Object> config = note.getConfig();
+    config.put("cron", null);
+    note.setConfig(config);
+    notebook.refreshCron(note.id());
+    
+    return new JsonResponse(Status.OK).build();
+  }  
+  
+  /**
+   * Get cron job REST API
+   * @param
+   * @return JSON with status.OK
+   * @throws IOException, IllegalArgumentException
+   */
+  @GET
+  @Path("cron/{notebookId}")
+  public Response getCronJob(@PathParam("notebookId") String notebookId) throws
+      IOException, IllegalArgumentException {
+    logger.info("Get cron job note {}", notebookId);
+
+    Note note = notebook.getNote(notebookId);
+    if (note == null) {
+      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
+    }
+    
+    return new JsonResponse(Status.OK, note.getConfig().get("cron")).build();
+  }  
 }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/CronRequest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/CronRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/CronRequest.java
new file mode 100644
index 0000000..5a33931
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/CronRequest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.zeppelin.rest.message;
+
+import java.util.Map;
+
+import org.apache.zeppelin.interpreter.InterpreterOption;
+
+/**
+ *  CronRequest rest api request message
+ *
+ */
+public class CronRequest {
+  String cron;
+
+  public CronRequest (){
+
+  }
+
+  public String getCronString() {
+    return cron;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
index 1895e16..db7affe 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AbstractTestRestApi.java
@@ -393,6 +393,10 @@ public abstract class AbstractTestRestApi {
 
   protected Matcher<? super HttpMethodBase> isCreated() { return responsesWith(201); }
 
+  protected Matcher<? super HttpMethodBase> isBadRequest() { return responsesWith(400); }
+
+  protected Matcher<? super HttpMethodBase> isNotFound() { return responsesWith(404); }
+
   protected Matcher<? super HttpMethodBase> isNotAllowed() {
     return responsesWith(405);
   }

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
index 8468de2..0a58fb0 100644
--- a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
+++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
@@ -264,7 +264,7 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     LOG.info("testCloneNotebook");
     // Create note to clone
     Note note = ZeppelinServer.notebook.createNote();
-    assertNotNull("cant create new note", note);
+    assertNotNull("can't create new note", note);
     note.setName("source note for clone");
     Paragraph paragraph = note.addParagraph();
     Map config = paragraph.getConfig();
@@ -308,5 +308,109 @@ public class ZeppelinRestApiTest extends AbstractTestRestApi {
     get.releaseConnection();
   }
 
+  @Test
+  public void testNoteJobs() throws IOException, InterruptedException {
+    LOG.info("testNoteJobs");
+    // Create note to run test.
+    Note note = ZeppelinServer.notebook.createNote();
+    assertNotNull("can't create new note", note);
+    note.setName("note for run test");
+    Paragraph paragraph = note.addParagraph();
+    
+    Map config = paragraph.getConfig();
+    config.put("enabled", true);
+    paragraph.setConfig(config);
+    
+    paragraph.setText("%md This is test paragraph.");
+    note.persist();
+    String noteID = note.getId();
+
+    note.runAll();
+    // wait until job is finished or timeout.
+    int timeout = 1;
+    while (!paragraph.isTerminated()) {
+      Thread.sleep(1000);
+      if (timeout++ > 10) {
+        LOG.info("testNoteJobs timeout job.");
+        break;
+      }
+    }
+    
+    // Call Run Notebook Jobs REST API
+    PostMethod postNoteJobs = httpPost("/notebook/job/" + noteID, "");
+    assertThat("test notebook jobs run:", postNoteJobs, isAllowed());
+    postNoteJobs.releaseConnection();
+
+    // Call Stop Notebook Jobs REST API
+    DeleteMethod deleteNoteJobs = httpDelete("/notebook/job/" + noteID);
+    assertThat("test notebook stop:", deleteNoteJobs, isAllowed());
+    deleteNoteJobs.releaseConnection();    
+    Thread.sleep(1000);
+    
+    // Call Run paragraph REST API
+    PostMethod postParagraph = httpPost("/notebook/job/" + noteID + "/" + paragraph.getId(), "");
+    assertThat("test paragraph run:", postParagraph, isAllowed());
+    postParagraph.releaseConnection();    
+    Thread.sleep(1000);
+    
+    // Call Stop paragraph REST API
+    DeleteMethod deleteParagraph = httpDelete("/notebook/job/" + noteID + "/" + paragraph.getId());
+    assertThat("test paragraph stop:", deleteParagraph, isAllowed());
+    deleteParagraph.releaseConnection();    
+    Thread.sleep(1000);
+    
+    //cleanup
+    ZeppelinServer.notebook.removeNote(note.getId());
+  }
+  
+  @Test
+  public void testCronJobs() throws InterruptedException, IOException{
+    // create a note and a paragraph
+    Note note = ZeppelinServer.notebook.createNote();
+
+    note.setName("note for run test");
+    Paragraph paragraph = note.addParagraph();
+    paragraph.setText("%md This is test paragraph.");
+    
+    Map config = paragraph.getConfig();
+    config.put("enabled", true);
+    paragraph.setConfig(config);
+
+    note.runAll();
+    // wait until job is finished or timeout.
+    int timeout = 1;
+    while (!paragraph.isTerminated()) {
+      Thread.sleep(1000);
+      if (timeout++ > 10) {
+        LOG.info("testNoteJobs timeout job.");
+        break;
+      }
+    }
+    
+    String jsonRequest = "{\"cron\":\"* * * * * ?\" }";
+    // right cron expression but not exist note.
+    PostMethod postCron = httpPost("/notebook/cron/notexistnote", jsonRequest);
+    assertThat("", postCron, isNotFound());
+    postCron.releaseConnection();
+    
+    // right cron expression.
+    postCron = httpPost("/notebook/cron/" + note.getId(), jsonRequest);
+    assertThat("", postCron, isAllowed());
+    postCron.releaseConnection();
+    Thread.sleep(1000);
+    
+    // wrong cron expression.
+    jsonRequest = "{\"cron\":\"a * * * * ?\" }";
+    postCron = httpPost("/notebook/cron/" + note.getId(), jsonRequest);
+    assertThat("", postCron, isBadRequest());
+    postCron.releaseConnection();
+    Thread.sleep(1000);
+    
+    // remove cron job.
+    DeleteMethod deleteCron = httpDelete("/notebook/cron/" + note.getId());
+    assertThat("", deleteCron, isAllowed());
+    deleteCron.releaseConnection();
+    ZeppelinServer.notebook.removeNote(note.getId());
+  }  
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/664a13a2/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 66a63c8..93dcec5 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
@@ -293,6 +293,21 @@ public class Note implements Serializable, JobListener {
       return paragraphs.get(paragraphs.size() - 1);
     }
   }
+  
+  public List<Map<String, String>> generateParagraphsInfo (){
+    List<Map<String, String>> paragraphsInfo = new LinkedList<>();
+    synchronized (paragraphs) {
+      for (Paragraph p : paragraphs) {
+        Map<String, String> info = new HashMap<>();
+        info.put("id", p.getId());
+        info.put("status", p.getStatus().toString());
+        info.put("started", p.getDateStarted().toString());
+        info.put("finished", p.getDateFinished().toString());
+        paragraphsInfo.add(info);
+      }
+    }
+    return paragraphsInfo;
+  }  
 
   /**
    * Run all paragraphs sequentially.