You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@griffin.apache.org by gu...@apache.org on 2019/03/14 14:28:44 UTC

[griffin] branch master updated: [GRIFFIN-229] trigger the job right now with fixing comments

This is an automated email from the ASF dual-hosted git repository.

guoyp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/griffin.git


The following commit(s) were added to refs/heads/master by this push:
     new 2264a28  [GRIFFIN-229] trigger the job right now with fixing comments
2264a28 is described below

commit 2264a28d8fb04664a67f5107bb39b7185975e9d7
Author: Borgatin Alexandr <ab...@griddynamics.com>
AuthorDate: Thu Mar 14 22:28:36 2019 +0800

    [GRIFFIN-229] trigger the job right now with fixing comments
    
    [comment](https://github.com/apache/griffin/pull/480#issuecomment-460075951)
    > dyingbleed can you please include screenshots for UI changes?
    
    <img width="1074" alt="screen shot 2019-03-06 at 18 19 25" src="https://user-images.githubusercontent.com/43031807/53944639-d4424980-40d0-11e9-977e-29b45b879bf9.png">
    <img width="1065" alt="screen shot 2019-03-06 at 18 43 23" src="https://user-images.githubusercontent.com/43031807/53944659-db695780-40d0-11e9-9114-43879cf068fa.png">
    
    Author: Borgatin Alexandr <ab...@griddynamics.com>
    Author: Alexandr Borgatin <43...@users.noreply.github.com>
    Author: Anthony Li <li...@gmail.com>
    
    Closes #485 from aborgatin/feature/GRIFFIN-229.
---
 griffin-doc/service/api-guide.md                   | 10 ++++
 griffin-doc/service/postman/griffin.json           | 32 +++++++++++++
 .../org/apache/griffin/core/job/JobController.java |  6 +++
 .../org/apache/griffin/core/job/JobService.java    |  2 +
 .../apache/griffin/core/job/JobServiceImpl.java    | 28 +++++++----
 .../apache/griffin/core/job/JobControllerTest.java | 19 ++++++++
 .../griffin/core/job/JobServiceImplTest.java       | 56 ++++++++++++++++++++++
 ui/angular/src/app/job/job.component.html          |  5 +-
 ui/angular/src/app/job/job.component.ts            | 25 ++++++++++
 ui/angular/src/app/service/service.service.ts      |  1 +
 10 files changed, 174 insertions(+), 10 deletions(-)

diff --git a/griffin-doc/service/api-guide.md b/griffin-doc/service/api-guide.md
index 30bbcc7..95113af 100644
--- a/griffin-doc/service/api-guide.md
+++ b/griffin-doc/service/api-guide.md
@@ -36,6 +36,7 @@ Apache Griffin default `BASE_PATH` is `http://<your ip>:8080`.
 
 - [Griffin Jobs](#3)
     - [Add Job](#31)
+    - [Trigger job by id](37)
     - [Get Job](#32)
     - [Remove Job](#33)
     - [Get Job Instances](#34)
@@ -542,6 +543,15 @@ curl -k -H "Content-Type: application/json" -H "Accept: application/json" \
 }'
 ```
 
+<div id = "37"></div>
+
+### Trigger job by id
+`POST /api/v1/jobs/trigger/{job_id}`
+#### API Example
+```
+curl -k -X POST http://127.0.0.1:8080/api/v1/jobs/trigger/51
+```
+
 <div id = "32"></div>
 
 ### Get all jobs
diff --git a/griffin-doc/service/postman/griffin.json b/griffin-doc/service/postman/griffin.json
index ac64412..e789121 100644
--- a/griffin-doc/service/postman/griffin.json
+++ b/griffin-doc/service/postman/griffin.json
@@ -1592,6 +1592,38 @@
 					]
 				},
 				{
+					"name": "Trigger job by id",
+					"request": {
+						"method": "POST",
+						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": ""
+						},
+						"url": {
+							"raw": "{{BASE_PATH}}/api/v1/jobs/trigger/:id",
+							"host": [
+								"{{BASE_PATH}}"
+							],
+							"path": [
+								"api",
+								"v1",
+								"jobs",
+								"trigger",
+								":id"
+							],
+							"variable": [
+								{
+									"key": "id",
+									"value": ""
+								}
+							]
+						},
+						"description": "`POST /api/v1/jobs/trigger/{id}`\n\n#### Path Variable\n- id -`required` `Long` job id\n\n#### Response\nThe response body should be empty if no error happens, and the HTTP status is (204, \"No Content\").\n\nIt may return failed messages. For example\n```\n{\n    \"timestamp\": 1517208792108,\n    \"status\": 404,\n    \"error\": \"Not Found\",\n    \"code\": 40402,\n    \"message\": \"Job id does not exist\",\n    \"path\": \"/api/v1/jobs/trigger/2\"\n}\n```\nTher [...]
+					},
+					"response": []
+				},
+				{
 					"name": "Delete  job by name",
 					"request": {
 						"method": "DELETE",
diff --git a/service/src/main/java/org/apache/griffin/core/job/JobController.java b/service/src/main/java/org/apache/griffin/core/job/JobController.java
index f4ee791..b5274a8 100644
--- a/service/src/main/java/org/apache/griffin/core/job/JobController.java
+++ b/service/src/main/java/org/apache/griffin/core/job/JobController.java
@@ -113,4 +113,10 @@ public class JobController {
                 .contentType(MediaType.APPLICATION_OCTET_STREAM)
                 .body(resource);
     }
+
+    @RequestMapping(value = "/jobs/trigger/{id}", method = RequestMethod.POST)
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void triggerJob(@PathVariable("id") Long id) throws SchedulerException {
+        jobService.triggerJobById(id);
+    }
 }
diff --git a/service/src/main/java/org/apache/griffin/core/job/JobService.java b/service/src/main/java/org/apache/griffin/core/job/JobService.java
index 58e541f..42415d9 100644
--- a/service/src/main/java/org/apache/griffin/core/job/JobService.java
+++ b/service/src/main/java/org/apache/griffin/core/job/JobService.java
@@ -45,4 +45,6 @@ public interface JobService {
     JobHealth getHealthInfo();
 
     String getJobHdfsSinksPath(String jobName, long timestamp);
+
+    void triggerJobById(Long id) throws SchedulerException;
 }
diff --git a/service/src/main/java/org/apache/griffin/core/job/JobServiceImpl.java b/service/src/main/java/org/apache/griffin/core/job/JobServiceImpl.java
index 7dc7f6d..a7fc546 100644
--- a/service/src/main/java/org/apache/griffin/core/job/JobServiceImpl.java
+++ b/service/src/main/java/org/apache/griffin/core/job/JobServiceImpl.java
@@ -45,14 +45,7 @@ import org.apache.griffin.core.util.JsonUtil;
 import org.apache.griffin.core.util.YarnNetUtil;
 import org.json.JSONArray;
 import org.json.JSONObject;
-import org.quartz.JobDataMap;
-import org.quartz.JobDetail;
-import org.quartz.JobKey;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.Trigger;
-import org.quartz.TriggerBuilder;
-import org.quartz.TriggerKey;
+import org.quartz.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -99,7 +92,7 @@ import static org.apache.griffin.core.measure.entity.GriffinMeasure.ProcessType.
 import static org.quartz.CronScheduleBuilder.cronSchedule;
 import static org.quartz.JobBuilder.newJob;
 import static org.quartz.JobKey.jobKey;
-import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
+import static org.quartz.SimpleScheduleBuilder.*;
 import static org.quartz.TriggerBuilder.newTrigger;
 import static org.quartz.TriggerKey.triggerKey;
 
@@ -661,4 +654,21 @@ public class JobServiceImpl implements JobService {
             return null;
         }
     }
+
+    @Override
+    public void triggerJobById(Long id) throws SchedulerException {
+        AbstractJob job = jobRepo.findByIdAndDeleted(id, false);
+        validateJobExist(job);
+        Scheduler scheduler = factory.getScheduler();
+        JobKey jobKey = jobKey(job.getName(), job.getGroup());
+        if (scheduler.checkExists(jobKey)) {
+            Trigger trigger = TriggerBuilder.newTrigger()
+                    .forJob(jobKey)
+                    .startNow()
+                    .build();
+            scheduler.scheduleJob(trigger);
+        } else {
+            LOGGER.warn("Could not trigger job id {}.", id);
+        }
+    }
 }
diff --git a/service/src/test/java/org/apache/griffin/core/job/JobControllerTest.java b/service/src/test/java/org/apache/griffin/core/job/JobControllerTest.java
index 0bd74e6..ab39b16 100644
--- a/service/src/test/java/org/apache/griffin/core/job/JobControllerTest.java
+++ b/service/src/test/java/org/apache/griffin/core/job/JobControllerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -171,4 +172,22 @@ public class JobControllerTest {
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.healthyJobCount", is(1)));
     }
+
+    @Test
+    public void testTriggerJobForSuccess() throws Exception {
+        doNothing().when(service).triggerJobById(1L);
+
+        mvc.perform(post(URLHelper.API_VERSION_PATH + "/jobs/trigger/1"))
+                .andExpect(status().isNoContent());
+    }
+
+    @Test
+    public void testTriggerJobForFailureWithException() throws Exception {
+        doThrow(new GriffinException.ServiceException("Failed to trigger job",
+                new Exception()))
+                .when(service).triggerJobById(1L);
+
+        mvc.perform(post(URLHelper.API_VERSION_PATH + "/jobs/trigger/1"))
+                .andExpect(status().isInternalServerError());
+    }
 }
diff --git a/service/src/test/java/org/apache/griffin/core/job/JobServiceImplTest.java b/service/src/test/java/org/apache/griffin/core/job/JobServiceImplTest.java
new file mode 100644
index 0000000..a335de2
--- /dev/null
+++ b/service/src/test/java/org/apache/griffin/core/job/JobServiceImplTest.java
@@ -0,0 +1,56 @@
+package org.apache.griffin.core.job;
+
+import org.apache.griffin.core.exception.GriffinException;
+import org.apache.griffin.core.job.entity.AbstractJob;
+import org.apache.griffin.core.job.repo.JobRepo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.apache.griffin.core.util.EntityMocksHelper.createGriffinJob;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+@RunWith(SpringRunner.class)
+public class JobServiceImplTest {
+
+    @Mock
+    private JobRepo<AbstractJob> jobRepo;
+
+    @Mock
+    private SchedulerFactoryBean factory;
+
+    @InjectMocks
+    private JobServiceImpl jobService;
+
+
+    @Test
+    public void testTriggerJobById() throws SchedulerException {
+        Long jobId = 1L;
+        AbstractJob job = createGriffinJob();
+        given(jobRepo.findByIdAndDeleted(jobId,false)).willReturn(job);
+        Scheduler scheduler = mock(Scheduler.class);
+        given(scheduler.checkExists(any(JobKey.class))).willReturn(true);
+        given(factory.getScheduler()).willReturn(scheduler);
+        jobService.triggerJobById(jobId);
+
+        verify(scheduler, times(1)).scheduleJob(any());
+    }
+
+
+    @Test(expected = GriffinException.NotFoundException.class)
+    public void testTriggerJobByIdFail() throws SchedulerException {
+        Long jobId = 1L;
+        given(jobRepo.findByIdAndDeleted(jobId,false)).willReturn(null);
+        jobService.triggerJobById(jobId);
+    }
+}
diff --git a/ui/angular/src/app/job/job.component.html b/ui/angular/src/app/job/job.component.html
index 2fb87a2..2b7cd9d 100644
--- a/ui/angular/src/app/job/job.component.html
+++ b/ui/angular/src/app/job/job.component.html
@@ -77,7 +77,7 @@ under the License.
           &nbsp;
           <a (click)="remove(row)" title="delete" style="text-decoration:none">
             <i class="fa fa-trash-o po"></i>
-          </a> &nbsp;
+          </a>&nbsp;
           <a routerLink="/job/{{row.id}}" title="subscribe">
             <i class="fa fa-eye"></i>
           </a>&nbsp;
@@ -86,6 +86,9 @@ under the License.
           </a>
           <a *ngIf="row.action!=='START'" (click)="stateMag(row)" title="Stop" style="text-decoration:none">
             <i class="fa fa-stop"></i>
+          </a>&nbsp;
+          <a (click)="trigger(row)" title="trigger now" style="text-decoration:none">
+            <i class="fa fa-caret-square-o-right po"></i>
           </a>
         </td>
         <td>
diff --git a/ui/angular/src/app/job/job.component.ts b/ui/angular/src/app/job/job.component.ts
index 0a86fe5..893db64 100644
--- a/ui/angular/src/app/job/job.component.ts
+++ b/ui/angular/src/app/job/job.component.ts
@@ -45,6 +45,7 @@ export class JobComponent implements OnInit {
   action: string;
   modalWndMsg: string;
   isStop: boolean;
+  isTrigger: boolean;
 
   private toasterService: ToasterService;
 
@@ -101,6 +102,19 @@ export class JobComponent implements OnInit {
           console.log("Error when manage job state");
         });
     }
+    else if (this.isTrigger) {
+      $("#save").attr("disabled", "true");
+      let actionUrl = this.serviceService.config.uri.triggerJobById + "/" + this.deleteId;
+      this.http.post(actionUrl, {}).subscribe(data => {
+          let self = this;
+          self.hide();
+          this.isTrigger = false;
+        },
+        err => {
+          this.toasterService.pop("error", "Error!", "Failed to trigger job!");
+          console.log("Error when trigger job");
+        });
+    }
     else {
       let deleteJob = this.serviceService.config.uri.deleteJob;
       let deleteUrl = deleteJob + "/" + this.deleteId;
@@ -196,4 +210,15 @@ export class JobComponent implements OnInit {
       this.results = Object.assign([], trans).reverse();
     });
   }
+
+  trigger(row): void {
+    $("#save").removeAttr("disabled");
+    this.modalWndMsg = "Trigger the job with the below information?";
+    this.visible = true;
+    setTimeout(() => (this.visibleAnimate = true), 100);
+    this.deletedRow = row;
+    this.deleteIndex = this.results.indexOf(row);
+    this.deleteId = row.id;
+    this.isTrigger = true;
+  }
 }
diff --git a/ui/angular/src/app/service/service.service.ts b/ui/angular/src/app/service/service.service.ts
index 7d50b4b..57093f4 100644
--- a/ui/angular/src/app/service/service.service.ts
+++ b/ui/angular/src/app/service/service.service.ts
@@ -92,6 +92,7 @@ export class ServiceService {
       addJobs: this.BACKEND_SERVER + this.API_ROOT_PATH + "/jobs",
       modifyJobs: this.BACKEND_SERVER + this.API_ROOT_PATH + "/jobs",
       getJobById: this.BACKEND_SERVER + this.API_ROOT_PATH + "/jobs/config",
+      triggerJobById: this.BACKEND_SERVER + this.API_ROOT_PATH + "/jobs/trigger",
       getMeasuresByOwner:
       this.BACKEND_SERVER + this.API_ROOT_PATH + "/measures/owner/",
       deleteJob: this.BACKEND_SERVER + this.API_ROOT_PATH + "/jobs",