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

journey pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 1c77708  Supplementary data by schedule (#1830)
1c77708 is described below

commit 1c77708a89a6dfd6a726fff74c9ff66ad3247778
Author: 老佛爷 <[email protected]>
AuthorDate: Thu Jan 16 15:33:00 2020 +0800

    Supplementary data by schedule (#1830)
    
    * Supplementary data by schedule
    
    * fix sonar check bug
    
    * fix code duplicated blocks
    
    * ut
    
    * loop by day
    
    * MasterExecThread test
    
    * test add licene
    
    Co-authored-by: dailidong <[email protected]>
---
 .../api/service/ExecutorService.java               |  53 +++--
 .../api/service/ExecutorService2Test.java          | 229 +++++++++++++++++++++
 .../apache/dolphinscheduler/dao/ProcessDao.java    |   9 +
 .../dao/mapper/ScheduleMapper.java                 |   7 +
 .../dolphinscheduler/dao/mapper/ScheduleMapper.xml |   5 +
 dolphinscheduler-server/pom.xml                    |  21 ++
 .../server/master/runner/MasterExecThread.java     |  50 ++++-
 .../server/utils/ScheduleUtils.java                |  79 +++++++
 .../server/master/MasterExecThreadTest.java        | 154 ++++++++++++++
 .../server/utils/ScheduleUtilsTest.java            |  44 ++++
 .../pages/definition/pages/list/_source/start.vue  |   2 +-
 .../src/js/module/i18n/locale/en_US.js             |   1 +
 .../src/js/module/i18n/locale/zh_CN.js             |   1 +
 pom.xml                                            |   3 +
 14 files changed, 636 insertions(+), 22 deletions(-)

diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ExecutorService.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ExecutorService.java
index 576f0c3..c1689c5 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ExecutorService.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ExecutorService.java
@@ -21,6 +21,7 @@ import org.apache.dolphinscheduler.api.enums.ExecuteType;
 import org.apache.dolphinscheduler.api.enums.Status;
 import org.apache.dolphinscheduler.common.Constants;
 import org.apache.dolphinscheduler.common.enums.*;
+import org.apache.dolphinscheduler.common.utils.CollectionUtils;
 import org.apache.dolphinscheduler.common.utils.DateUtils;
 import org.apache.dolphinscheduler.common.utils.JSONUtils;
 import org.apache.dolphinscheduler.common.utils.StringUtils;
@@ -29,6 +30,7 @@ import org.apache.dolphinscheduler.dao.entity.*;
 import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper;
 import org.apache.dolphinscheduler.dao.mapper.ProcessInstanceMapper;
 import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
+import org.apache.dolphinscheduler.server.utils.ScheduleUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -499,22 +501,47 @@ public class ExecutorService extends BaseService{
 
         if(commandType == CommandType.COMPLEMENT_DATA){
             runMode = (runMode == null) ? RunMode.RUN_MODE_SERIAL : runMode;
-            if(runMode == RunMode.RUN_MODE_SERIAL){
-                cmdParam.put(CMDPARAM_COMPLEMENT_DATA_START_DATE, 
DateUtils.dateToString(start));
-                cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, 
DateUtils.dateToString(end));
-                command.setCommandParam(JSONUtils.toJson(cmdParam));
-                return processDao.createCommand(command);
-            }else if (runMode == RunMode.RUN_MODE_PARALLEL){
-                int runCunt = 0;
-                while(!start.after(end)){
-                    runCunt += 1;
+            if(null != start && null != end && start.before(end)){
+                if(runMode == RunMode.RUN_MODE_SERIAL){
                     cmdParam.put(CMDPARAM_COMPLEMENT_DATA_START_DATE, 
DateUtils.dateToString(start));
-                    cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, 
DateUtils.dateToString(start));
+                    cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, 
DateUtils.dateToString(end));
                     command.setCommandParam(JSONUtils.toJson(cmdParam));
-                    processDao.createCommand(command);
-                    start = DateUtils.getSomeDay(start, 1);
+                    return processDao.createCommand(command);
+                }else if (runMode == RunMode.RUN_MODE_PARALLEL){
+                    List<Schedule> schedules = 
processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefineId);
+                    List<Date> listDate = new LinkedList<>();
+                    if(!CollectionUtils.isEmpty(schedules)){
+                        for (Schedule item : schedules) {
+                            List<Date> list = 
ScheduleUtils.getRecentTriggerTime(item.getCrontab(), start, end);
+                            listDate.addAll(list);
+                        }
+                    }
+                    if(!CollectionUtils.isEmpty(listDate)){
+                        // loop by schedule date
+                        for (Date date : listDate) {
+                            cmdParam.put(CMDPARAM_COMPLEMENT_DATA_START_DATE, 
DateUtils.dateToString(date));
+                            cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, 
DateUtils.dateToString(date));
+                            
command.setCommandParam(JSONUtils.toJson(cmdParam));
+                            processDao.createCommand(command);
+                        }
+                        return listDate.size();
+                    }else{
+                        // loop by day
+                        int runCunt = 0;
+                        while(!start.after(end)) {
+                            runCunt += 1;
+                            cmdParam.put(CMDPARAM_COMPLEMENT_DATA_START_DATE, 
DateUtils.dateToString(start));
+                            cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, 
DateUtils.dateToString(start));
+                            
command.setCommandParam(JSONUtils.toJson(cmdParam));
+                            processDao.createCommand(command);
+                            start = DateUtils.getSomeDay(start, 1);
+                        }
+                        return runCunt;
+                    }
                 }
-                return runCunt;
+            }else{
+                logger.error("there is not vaild schedule date for the process 
definition: id:{},date:{}",
+                        processDefineId, schedule);
             }
         }else{
             command.setCommandParam(JSONUtils.toJson(cmdParam));
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorService2Test.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorService2Test.java
new file mode 100644
index 0000000..b4f3e7e
--- /dev/null
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorService2Test.java
@@ -0,0 +1,229 @@
+/*
+ * 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.dolphinscheduler.api.service;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.common.Constants;
+import org.apache.dolphinscheduler.common.enums.CommandType;
+import org.apache.dolphinscheduler.common.enums.Priority;
+import org.apache.dolphinscheduler.common.enums.ReleaseState;
+import org.apache.dolphinscheduler.common.enums.RunMode;
+import org.apache.dolphinscheduler.dao.ProcessDao;
+import org.apache.dolphinscheduler.dao.entity.*;
+import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper;
+import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.text.ParseException;
+import java.util.*;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+/**
+ * test for ExecutorService
+ */
+@RunWith(MockitoJUnitRunner.Silent.class)
+public class ExecutorService2Test {
+
+    @InjectMocks
+    private ExecutorService executorService;
+
+    @Mock
+    private ProcessDao processDao;
+
+    @Mock
+    private ProcessDefinitionMapper processDefinitionMapper;
+
+    @Mock
+    private ProjectMapper projectMapper;
+
+    @Mock
+    private ProjectService projectService;
+
+    private int processDefinitionId = 1;
+
+    private int tenantId = 1;
+
+    private int userId = 1;
+
+    private ProcessDefinition processDefinition = new ProcessDefinition();
+
+    private User loginUser = new User();
+
+    private String projectName = "projectName";
+
+    private Project project = new Project();
+
+    private String cronTime;
+
+    @Before
+    public void init(){
+        // user
+        loginUser.setId(userId);
+
+        // processDefinition
+        processDefinition.setId(processDefinitionId);
+        processDefinition.setReleaseState(ReleaseState.ONLINE);
+        processDefinition.setTenantId(tenantId);
+        processDefinition.setUserId(userId);
+
+        // project
+        project.setName(projectName);
+
+        // cronRangeTime
+        cronTime = "2020-01-01 00:00:00,2020-01-31 23:00:00";
+
+        // mock
+        
Mockito.when(projectMapper.queryByName(projectName)).thenReturn(project);
+        Mockito.when(projectService.checkProjectAndAuth(loginUser, project, 
projectName)).thenReturn(checkProjectAndAuth());
+        
Mockito.when(processDefinitionMapper.selectById(processDefinitionId)).thenReturn(processDefinition);
+        Mockito.when(processDao.getTenantForProcess(tenantId, 
userId)).thenReturn(new Tenant());
+        
Mockito.when(processDao.createCommand(any(Command.class))).thenReturn(1);
+    }
+
+    /**
+     * not complement
+     * @throws ParseException
+     */
+    @Test
+    public void testNoComplement() throws ParseException {
+        try {
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(zeroSchedulerList());
+            Map<String, Object> result = 
executorService.execProcessInstance(loginUser, projectName,
+                    processDefinitionId, cronTime, CommandType.START_PROCESS,
+                    null, null,
+                    null, null, 0,
+                    "", "", RunMode.RUN_MODE_SERIAL,
+                    Priority.LOW, 0, 110);
+            Assert.assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
+            verify(processDao, times(1)).createCommand(any(Command.class));
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    /**
+     * date error
+     * @throws ParseException
+     */
+    @Test
+    public void testDateError() throws ParseException {
+        try {
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(zeroSchedulerList());
+            Map<String, Object> result = 
executorService.execProcessInstance(loginUser, projectName,
+                    processDefinitionId, "2020-01-31 23:00:00,2020-01-01 
00:00:00", CommandType.COMPLEMENT_DATA,
+                    null, null,
+                    null, null, 0,
+                    "", "", RunMode.RUN_MODE_SERIAL,
+                    Priority.LOW, 0, 110);
+            Assert.assertEquals(Status.START_PROCESS_INSTANCE_ERROR, 
result.get(Constants.STATUS));
+            verify(processDao, times(0)).createCommand(any(Command.class));
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    /**
+     * serial
+     * @throws ParseException
+     */
+    @Test
+    public void testSerial() throws ParseException {
+        try {
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(zeroSchedulerList());
+            Map<String, Object> result = 
executorService.execProcessInstance(loginUser, projectName,
+                    processDefinitionId, cronTime, CommandType.COMPLEMENT_DATA,
+                    null, null,
+                    null, null, 0,
+                    "", "", RunMode.RUN_MODE_SERIAL,
+                    Priority.LOW, 0, 110);
+            Assert.assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
+            verify(processDao, times(1)).createCommand(any(Command.class));
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    /**
+     * without schedule
+     * @throws ParseException
+     */
+    @Test
+    public void testParallelWithOutSchedule() throws ParseException {
+        try{
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(zeroSchedulerList());
+            Map<String, Object> result = 
executorService.execProcessInstance(loginUser, projectName,
+                    processDefinitionId, cronTime, CommandType.COMPLEMENT_DATA,
+                    null, null,
+                    null, null, 0,
+                    "", "", RunMode.RUN_MODE_PARALLEL,
+                    Priority.LOW, 0, 110);
+            Assert.assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
+            verify(processDao, times(31)).createCommand(any(Command.class));
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    /**
+     * with schedule
+     * @throws ParseException
+     */
+    @Test
+    public void testParallelWithSchedule() throws ParseException {
+        try{
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(oneSchedulerList());
+            Map<String, Object> result = 
executorService.execProcessInstance(loginUser, projectName,
+                    processDefinitionId, cronTime, CommandType.COMPLEMENT_DATA,
+                    null, null,
+                    null, null, 0,
+                    "", "", RunMode.RUN_MODE_PARALLEL,
+                    Priority.LOW, 0, 110);
+            Assert.assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
+            verify(processDao, times(16)).createCommand(any(Command.class));
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    private List<Schedule> zeroSchedulerList(){
+        return Collections.EMPTY_LIST;
+    }
+
+    private List<Schedule> oneSchedulerList(){
+        List<Schedule> schedulerList = new LinkedList<>();
+        Schedule schedule = new Schedule();
+        schedule.setCrontab("0 0 0 1/2 * ?");
+        schedulerList.add(schedule);
+        return schedulerList;
+    }
+
+    private Map<String, Object> checkProjectAndAuth(){
+        Map<String, Object> result = new HashMap<>();
+        result.put(Constants.STATUS, Status.SUCCESS);
+        return result;
+    }
+}
\ No newline at end of file
diff --git 
a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/ProcessDao.java
 
b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/ProcessDao.java
index 76e9d0b..820b2fd 100644
--- 
a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/ProcessDao.java
+++ 
b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/ProcessDao.java
@@ -1462,6 +1462,15 @@ public class ProcessDao {
     }
 
     /**
+     * query Schedule by processDefinitionId
+     * @param processDefinitionId processDefinitionId
+     * @see Schedule
+     */
+    public List<Schedule> queryReleaseSchedulerListByProcessDefinitionId(int 
processDefinitionId) {
+        return 
scheduleMapper.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId);
+    }
+
+    /**
      * query need failover process instance
      * @param host host
      * @return process instance list
diff --git 
a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.java
 
b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.java
index 3a1d125..8a49c8f 100644
--- 
a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.java
+++ 
b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.java
@@ -60,4 +60,11 @@ public interface ScheduleMapper extends BaseMapper<Schedule> 
{
      */
     List<Schedule> queryByProcessDefinitionId(@Param("processDefinitionId") 
int processDefinitionId);
 
+    /**
+     * query schedule list by process definition id
+     * @param processDefinitionId
+     * @return
+     */
+    List<Schedule> 
queryReleaseSchedulerListByProcessDefinitionId(@Param("processDefinitionId") 
int processDefinitionId);
+
 }
diff --git 
a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml
 
b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml
index 402c864..ddae96a 100644
--- 
a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml
+++ 
b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml
@@ -55,4 +55,9 @@
         from t_ds_schedules
         where process_definition_id =#{processDefinitionId}
     </select>
+    <select id="queryReleaseSchedulerListByProcessDefinitionId" 
resultType="org.apache.dolphinscheduler.dao.entity.Schedule">
+        select *
+        from t_ds_schedules
+        where process_definition_id =#{processDefinitionId} and release_state 
= 1
+    </select>
 </mapper>
\ No newline at end of file
diff --git a/dolphinscheduler-server/pom.xml b/dolphinscheduler-server/pom.xml
index 2ccc880..751fd91 100644
--- a/dolphinscheduler-server/pom.xml
+++ b/dolphinscheduler-server/pom.xml
@@ -111,6 +111,27 @@
                        <artifactId>dolphinscheduler-alert</artifactId>
                </dependency>
 
+               <dependency>
+                       <groupId>org.powermock</groupId>
+                       <artifactId>powermock-module-junit4</artifactId>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.powermock</groupId>
+                       <artifactId>powermock-api-mockito2</artifactId>
+                       <scope>test</scope>
+                       <exclusions>
+                               <exclusion>
+                                       <groupId>org.mockito</groupId>
+                                       <artifactId>mockito-core</artifactId>
+                               </exclusion>
+                       </exclusions>
+               </dependency>
+               <dependency>
+                       <groupId>org.mockito</groupId>
+                       <artifactId>mockito-core</artifactId>
+                       <scope>test</scope>
+               </dependency>
     </dependencies>
 
 
diff --git 
a/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/master/runner/MasterExecThread.java
 
b/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/master/runner/MasterExecThread.java
index a91f8c1..6c147e2 100644
--- 
a/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/master/runner/MasterExecThread.java
+++ 
b/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/master/runner/MasterExecThread.java
@@ -17,6 +17,7 @@
 package org.apache.dolphinscheduler.server.master.runner;
 
 import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Lists;
 import org.apache.commons.io.FileUtils;
 import org.apache.dolphinscheduler.common.Constants;
 import org.apache.dolphinscheduler.common.enums.*;
@@ -29,10 +30,12 @@ import 
org.apache.dolphinscheduler.common.thread.ThreadUtils;
 import org.apache.dolphinscheduler.common.utils.*;
 import org.apache.dolphinscheduler.dao.ProcessDao;
 import org.apache.dolphinscheduler.dao.entity.ProcessInstance;
+import org.apache.dolphinscheduler.dao.entity.Schedule;
 import org.apache.dolphinscheduler.dao.entity.TaskInstance;
 import org.apache.dolphinscheduler.dao.utils.DagHelper;
 import org.apache.dolphinscheduler.server.master.config.MasterConfig;
 import org.apache.dolphinscheduler.server.utils.AlertManager;
+import org.apache.dolphinscheduler.server.utils.ScheduleUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -203,10 +206,30 @@ public class MasterExecThread implements Runnable {
         Date startDate = 
DateUtils.getScheduleDate(cmdParam.get(CMDPARAM_COMPLEMENT_DATA_START_DATE));
         Date endDate = 
DateUtils.getScheduleDate(cmdParam.get(CMDPARAM_COMPLEMENT_DATA_END_DATE));
         processDao.saveProcessInstance(processInstance);
-        Date scheduleDate = processInstance.getScheduleTime();
 
-        if(scheduleDate == null){
-            scheduleDate = startDate;
+        // get schedules
+        int processDefinitionId = processInstance.getProcessDefinitionId();
+        List<Schedule> schedules = 
processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId);
+        List<Date> listDate = Lists.newLinkedList();
+        if(!CollectionUtils.isEmpty(schedules)){
+            for (Schedule schedule : schedules) {
+                List<Date> list = 
ScheduleUtils.getRecentTriggerTime(schedule.getCrontab(), startDate, endDate);
+                listDate.addAll(list);
+            }
+        }
+        // get first fire date
+        Iterator<Date> iterator = null;
+        Date scheduleDate = null;
+        if(!CollectionUtils.isEmpty(listDate)) {
+            iterator = listDate.iterator();
+            scheduleDate = iterator.next();
+            processInstance.setScheduleTime(scheduleDate);
+            processDao.updateProcessInstance(processInstance);
+        }else{
+            scheduleDate = processInstance.getScheduleTime();
+            if(scheduleDate == null){
+                scheduleDate = startDate;
+            }
         }
 
         while(Stopper.isRunning()){
@@ -232,11 +255,22 @@ public class MasterExecThread implements Runnable {
             }
 
             //  current process instance sucess ,next execute
-            scheduleDate = DateUtils.getSomeDay(scheduleDate, 1);
-            if(scheduleDate.after(endDate)){
-                // all success
-                logger.info("process {} complement completely!", 
processInstance.getId());
-                break;
+            if(null == iterator){
+                // loop by day
+                scheduleDate = DateUtils.getSomeDay(scheduleDate, 1);
+                if(scheduleDate.after(endDate)){
+                    // all success
+                    logger.info("process {} complement completely!", 
processInstance.getId());
+                    break;
+                }
+            }else{
+                // loop by schedule date
+                if(!iterator.hasNext()){
+                    // all success
+                    logger.info("process {} complement completely!", 
processInstance.getId());
+                    break;
+                }
+                scheduleDate = iterator.next();
             }
 
             logger.info("process {} start to complement {} data",
diff --git 
a/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/utils/ScheduleUtils.java
 
b/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/utils/ScheduleUtils.java
new file mode 100644
index 0000000..11730b9
--- /dev/null
+++ 
b/dolphinscheduler-server/src/main/java/org/apache/dolphinscheduler/server/utils/ScheduleUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dolphinscheduler.server.utils;
+
+import org.quartz.impl.triggers.CronTriggerImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ScheduleUtils
+ */
+public class ScheduleUtils {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ScheduleUtils.class);
+
+    /**
+     * Get the execution time of the time interval
+     * @param cron
+     * @param from
+     * @param to
+     * @return
+     */
+    public static List<Date> getRecentTriggerTime(String cron, Date from, Date 
to) {
+        return getRecentTriggerTime(cron, Integer.MAX_VALUE, from, to);
+    }
+
+    /**
+     * Get the execution time of the time interval
+     * @param cron
+     * @param size
+     * @param from
+     * @param to
+     * @return
+     */
+    public static List<Date> getRecentTriggerTime(String cron, int size, Date 
from, Date to) {
+        List list = new LinkedList<Date>();
+        if(to.before(from)){
+            logger.error("schedule date from:{} must before date to:{}!", 
from, to);
+            return list;
+        }
+        try {
+            CronTriggerImpl trigger = new CronTriggerImpl();
+            trigger.setCronExpression(cron);
+            trigger.setStartTime(from);
+            trigger.setEndTime(to);
+            trigger.computeFirstFireTime(null);
+            for (int i = 0; i < size; i++) {
+                Date schedule = trigger.getNextFireTime();
+                if(null == schedule){
+                    break;
+                }
+                list.add(schedule);
+                trigger.triggered(null);
+            }
+        } catch (ParseException e) {
+            logger.error("cron:{} error:{}", cron, e.getMessage());
+        }
+        return java.util.Collections.unmodifiableList(list);
+    }
+}
\ No newline at end of file
diff --git 
a/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/master/MasterExecThreadTest.java
 
b/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/master/MasterExecThreadTest.java
new file mode 100644
index 0000000..6f31e66
--- /dev/null
+++ 
b/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/master/MasterExecThreadTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.dolphinscheduler.server.master;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.dolphinscheduler.common.enums.*;
+import org.apache.dolphinscheduler.common.graph.DAG;
+import org.apache.dolphinscheduler.common.utils.DateUtils;
+import org.apache.dolphinscheduler.common.utils.SpringApplicationContext;
+import org.apache.dolphinscheduler.dao.ProcessDao;
+import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
+import org.apache.dolphinscheduler.dao.entity.ProcessInstance;
+import org.apache.dolphinscheduler.dao.entity.Schedule;
+import org.apache.dolphinscheduler.server.master.config.MasterConfig;
+import org.apache.dolphinscheduler.server.master.runner.MasterExecThread;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.springframework.context.ApplicationContext;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.text.ParseException;
+import java.util.*;
+import static 
org.apache.dolphinscheduler.common.Constants.CMDPARAM_COMPLEMENT_DATA_END_DATE;
+import static 
org.apache.dolphinscheduler.common.Constants.CMDPARAM_COMPLEMENT_DATA_START_DATE;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+
+/**
+ * test for MasterExecThread
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({MasterExecThread.class})
+public class MasterExecThreadTest {
+
+    private MasterExecThread masterExecThread;
+
+    private ProcessInstance processInstance;
+
+    private ProcessDao processDao;
+
+    private int processDefinitionId = 1;
+
+    private MasterConfig config;
+
+    private ApplicationContext applicationContext;
+
+    @Before
+    public void init() throws Exception{
+        processDao = mock(ProcessDao.class);
+
+        applicationContext = mock(ApplicationContext.class);
+        config = new MasterConfig();
+        config.setMasterExecTaskNum(1);
+        SpringApplicationContext springApplicationContext = new 
SpringApplicationContext();
+        springApplicationContext.setApplicationContext(applicationContext);
+        
Mockito.when(applicationContext.getBean(MasterConfig.class)).thenReturn(config);
+
+        processInstance = mock(ProcessInstance.class);
+        
Mockito.when(processInstance.getProcessDefinitionId()).thenReturn(processDefinitionId);
+        
Mockito.when(processInstance.getState()).thenReturn(ExecutionStatus.SUCCESS);
+        
Mockito.when(processInstance.getHistoryCmd()).thenReturn(CommandType.COMPLEMENT_DATA.toString());
+        Mockito.when(processInstance.getIsSubProcess()).thenReturn(Flag.NO);
+        
Mockito.when(processInstance.getScheduleTime()).thenReturn(DateUtils.stringToDate("2020-01-01
 00:00:00"));
+        Map<String, String> cmdParam = new HashMap<>();
+        cmdParam.put(CMDPARAM_COMPLEMENT_DATA_START_DATE, "2020-01-01 
00:00:00");
+        cmdParam.put(CMDPARAM_COMPLEMENT_DATA_END_DATE, "2020-01-31 23:00:00");
+        
Mockito.when(processInstance.getCommandParam()).thenReturn(JSONObject.toJSONString(cmdParam));
+        ProcessDefinition processDefinition = new ProcessDefinition();
+        processDefinition.setGlobalParamMap(Collections.EMPTY_MAP);
+        processDefinition.setGlobalParamList(Collections.EMPTY_LIST);
+        
Mockito.when(processInstance.getProcessDefinition()).thenReturn(processDefinition);
+
+        masterExecThread = PowerMockito.spy(new 
MasterExecThread(processInstance, processDao));
+        // prepareProcess init dag
+        Field dag = MasterExecThread.class.getDeclaredField("dag");
+        dag.setAccessible(true);
+        dag.set(masterExecThread, new DAG());
+        PowerMockito.doNothing().when(masterExecThread, "executeProcess");
+        PowerMockito.doNothing().when(masterExecThread, "postHandle");
+        PowerMockito.doNothing().when(masterExecThread, "prepareProcess");
+        PowerMockito.doNothing().when(masterExecThread, "runProcess");
+        PowerMockito.doNothing().when(masterExecThread, "endProcess");
+    }
+
+    /**
+     * without schedule
+     * @throws ParseException
+     */
+    @Test
+    public void testParallelWithOutSchedule() throws ParseException {
+        try{
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(zeroSchedulerList());
+            Method method = 
MasterExecThread.class.getDeclaredMethod("executeComplementProcess");
+            method.setAccessible(true);
+            method.invoke(masterExecThread);
+            // one create save, and 1-30 for next save, and last day 31 no save
+            verify(processDao, times(31)).saveProcessInstance(processInstance);
+        }catch (Exception e){
+            e.printStackTrace();
+            Assert.assertTrue(false);
+        }
+    }
+
+    /**
+     * with schedule
+     * @throws ParseException
+     */
+    @Test
+    public void testParallelWithSchedule() throws ParseException {
+        try{
+            
Mockito.when(processDao.queryReleaseSchedulerListByProcessDefinitionId(processDefinitionId)).thenReturn(oneSchedulerList());
+            Method method = 
MasterExecThread.class.getDeclaredMethod("executeComplementProcess");
+            method.setAccessible(true);
+            method.invoke(masterExecThread);
+            // one create save, and 15(1 to 31 step 2) for next save, and last 
day 31 no save
+            verify(processDao, times(16)).saveProcessInstance(processInstance);
+        }catch (Exception e){
+            Assert.assertTrue(false);
+        }
+    }
+
+    private List<Schedule> zeroSchedulerList(){
+        return Collections.EMPTY_LIST;
+    }
+
+    private List<Schedule> oneSchedulerList(){
+        List<Schedule> schedulerList = new LinkedList<>();
+        Schedule schedule = new Schedule();
+        schedule.setCrontab("0 0 0 1/2 * ?");
+        schedulerList.add(schedule);
+        return schedulerList;
+    }
+}
\ No newline at end of file
diff --git 
a/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/utils/ScheduleUtilsTest.java
 
b/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/utils/ScheduleUtilsTest.java
new file mode 100644
index 0000000..4fbbdab
--- /dev/null
+++ 
b/dolphinscheduler-server/src/test/java/org/apache/dolphinscheduler/server/utils/ScheduleUtilsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dolphinscheduler.server.utils;
+
+import org.apache.dolphinscheduler.common.utils.DateUtils;
+import org.junit.Test;
+import java.util.Date;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test ScheduleUtils
+ */
+public class ScheduleUtilsTest {
+
+    /**
+     * Test the getRecentTriggerTime method
+     */
+    @Test
+    public void testGetRecentTriggerTime() {
+        Date from = DateUtils.stringToDate("2020-01-01 00:00:00");
+        Date to = DateUtils.stringToDate("2020-01-31 01:00:00");
+        // test date
+        assertEquals(0, ScheduleUtils.getRecentTriggerTime("0 0 0 * * ? ", to, 
from).size());
+        // test error cron
+        assertEquals(0, ScheduleUtils.getRecentTriggerTime("0 0 0 * *", from, 
to).size());
+        // test cron
+        assertEquals(31, ScheduleUtils.getRecentTriggerTime("0 0 0 * * ? ", 
from, to).size());
+    }
+}
\ No newline at end of file
diff --git 
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
 
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
index 8672c41..0a22e22 100644
--- 
a/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
+++ 
b/dolphinscheduler-ui/src/js/conf/home/pages/projects/pages/definition/pages/list/_source/start.vue
@@ -141,7 +141,7 @@
       </div>
       <div class="clearfix list">
         <div class="text">
-          {{$t('Date')}}
+          {{$t('Schedule date')}}
         </div>
         <div class="cont">
           <x-datepicker
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js 
b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
index a77585e..2af5453 100644
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
@@ -361,6 +361,7 @@ export default {
   'Recipient': 'Recipient',
   'Cc': 'Cc',
   'Whether it is a complement process?': 'Whether it is a complement process?',
+  'Schedule date': 'Schedule date',
   'Mode of execution': 'Mode of execution',
   'Serial execution': 'Serial execution',
   'Parallel execution': 'Parallel execution',
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js 
b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
index 6784498..1c68e8e 100644
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
@@ -356,6 +356,7 @@ export default {
   'Recipient': '收件人',
   'Cc': '抄送人',
   'Whether it is a complement process?': '是否补数',
+  'Schedule date': '调度日期',
   'Mode of execution': '执行方式',
   'Serial execution': '串行执行',
   'Parallel execution': '并行执行',
diff --git a/pom.xml b/pom.xml
index 41c75f7..6d55c1e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -694,6 +694,7 @@
                                                
<include>**/api/service/ProcessDefinitionServiceTest.java</include>
                                                
<include>**/api/service/UdfFuncServiceTest.java</include>
                                                
<include>**/api/service/ResourcesServiceTest.java</include>
+                                               
<include>**/api/service/ExecutorService2Test.java</include>
                                                
<include>**/api/service/BaseServiceTest.java</include>
                                                
<include>**/api/service/BaseDAGServiceTest.java</include>
                                                
<include>**/alert/utils/ExcelUtilsTest.java</include>
@@ -703,6 +704,8 @@
                                                
<include>**/server/utils/SparkArgsUtilsTest.java</include>
                                                
<include>**/server/utils/FlinkArgsUtilsTest.java</include>
                                                
<include>**/server/utils/ParamUtilsTest.java</include>
+                                               
<include>**/server/utils/ScheduleUtilsTest.java</include>
+                                               
<include>**/server/master/MasterExecThreadTest.java</include>
                                                
<include>**/dao/mapper/AccessTokenMapperTest.java</include>
                                                
<include>**/dao/mapper/AlertGroupMapperTest.java</include>
                                                
<include>**/dao/mapper/AlertMapperTest.java</include>

Reply via email to