Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

package org.apache.dolphinscheduler.api.dto;

import org.apache.dolphinscheduler.common.enums.MisfirePolicy;
import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType;

import java.util.Date;

import lombok.Data;
Expand All @@ -31,6 +34,8 @@ public class ScheduleParam {
private Date endTime;
private String crontab;
private String timezoneId;
private ScheduleTriggerType triggerType = ScheduleTriggerType.CRON;
private MisfirePolicy misfirePolicy = MisfirePolicy.IGNORE_MISFIRES;

public ScheduleParam() {
}
Expand All @@ -40,6 +45,7 @@ public ScheduleParam(Date startTime, Date endTime, String timezoneId, String cro
this.endTime = endTime;
this.timezoneId = timezoneId;
this.crontab = crontab;
this.triggerType = ScheduleTriggerType.CRON;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@
import org.apache.dolphinscheduler.api.vo.ScheduleVO;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.FailureStrategy;
import org.apache.dolphinscheduler.common.enums.MisfirePolicy;
import org.apache.dolphinscheduler.common.enums.Priority;
import org.apache.dolphinscheduler.common.enums.ReleaseState;
import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType;
import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.dolphinscheduler.common.enums.WarningType;
import org.apache.dolphinscheduler.common.utils.DateUtils;
import org.apache.dolphinscheduler.common.utils.IntervalSchedule;
import org.apache.dolphinscheduler.common.utils.JSONUtils;
import org.apache.dolphinscheduler.dao.entity.Project;
import org.apache.dolphinscheduler.dao.entity.Schedule;
Expand All @@ -52,6 +55,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -167,11 +171,16 @@ public Schedule insertSchedule(User loginUser,

scheduleObj.setStartTime(scheduleParam.getStartTime());
scheduleObj.setEndTime(scheduleParam.getEndTime());
if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) {
if (!isValidScheduleExpression(scheduleParam)) {
log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab());
throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab());
}
scheduleObj.setCrontab(scheduleParam.getCrontab());
scheduleObj.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null
? MisfirePolicy.IGNORE_MISFIRES
: scheduleParam.getMisfirePolicy());
scheduleObj.setTriggerType(
scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType());
scheduleObj.setTimezoneId(scheduleParam.getTimezoneId());
scheduleObj.setWarningType(warningType);
scheduleObj.setWarningGroupId(warningGroupId);
Expand Down Expand Up @@ -387,6 +396,20 @@ public List<String> previewSchedule(User loginUser, String schedule) {
ZonedDateTime startTime = ZonedDateTime.ofInstant(scheduleParam.getStartTime().toInstant(), zoneId);
ZonedDateTime endTime = ZonedDateTime.ofInstant(scheduleParam.getEndTime().toInstant(), zoneId);
startTime = now.isAfter(startTime) ? now : startTime;
if (scheduleParam.getTriggerType() == ScheduleTriggerType.INTERVAL) {
IntervalSchedule intervalSchedule = IntervalSchedule.parse(scheduleParam.getCrontab());
List<ZonedDateTime> fireTimes = new ArrayList<>();
int executionLimit = intervalSchedule.getRepeatCount() < 0
? Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT
: Math.min(intervalSchedule.getRepeatCount() + 1, Constants.PREVIEW_SCHEDULE_EXECUTE_COUNT);
for (int i = 0; i < executionLimit && !startTime.isAfter(endTime); i++) {
fireTimes.add(startTime);
startTime = startTime.plus(Duration.ofMillis(intervalSchedule.getIntervalMilliseconds()));
}
return fireTimes.stream()
.map(t -> DateUtils.dateToString(t, zoneId))
.collect(Collectors.toList());
}

try {
cron = CronUtils.parse2Cron(scheduleParam.getCrontab());
Expand All @@ -401,6 +424,20 @@ public List<String> previewSchedule(User loginUser, String schedule) {
.collect(Collectors.toList());
}

private boolean isValidScheduleExpression(ScheduleParam scheduleParam) {
ScheduleTriggerType triggerType = scheduleParam.getTriggerType() == null
? ScheduleTriggerType.CRON
: scheduleParam.getTriggerType();
if (triggerType == ScheduleTriggerType.CRON) {
return CronUtils.isValidExpression(scheduleParam.getCrontab());
}
try {
IntervalSchedule.parse(scheduleParam.getCrontab());
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
/**
* update workflow definition schedule
*
Expand Down Expand Up @@ -552,11 +589,16 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe

schedule.setStartTime(scheduleParam.getStartTime());
schedule.setEndTime(scheduleParam.getEndTime());
if (!CronUtils.isValidExpression(scheduleParam.getCrontab())) {
if (!isValidScheduleExpression(scheduleParam)) {
log.error("Schedule crontab verify failure, crontab:{}.", scheduleParam.getCrontab());
throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab());
}
schedule.setCrontab(scheduleParam.getCrontab());
schedule.setMisfirePolicy(scheduleParam.getMisfirePolicy() == null
? MisfirePolicy.IGNORE_MISFIRES
: scheduleParam.getMisfirePolicy());
schedule.setTriggerType(
scheduleParam.getTriggerType() == null ? ScheduleTriggerType.CRON : scheduleParam.getTriggerType());
schedule.setTimezoneId(scheduleParam.getTimezoneId());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package org.apache.dolphinscheduler.api.vo;

import org.apache.dolphinscheduler.common.enums.FailureStrategy;
import org.apache.dolphinscheduler.common.enums.MisfirePolicy;
import org.apache.dolphinscheduler.common.enums.Priority;
import org.apache.dolphinscheduler.common.enums.ReleaseState;
import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType;
import org.apache.dolphinscheduler.common.enums.WarningType;
import org.apache.dolphinscheduler.common.utils.DateUtils;
import org.apache.dolphinscheduler.dao.entity.Schedule;
Expand Down Expand Up @@ -54,6 +56,10 @@ public class ScheduleVO {

private String crontab;

private ScheduleTriggerType triggerType;

private MisfirePolicy misfirePolicy;

private FailureStrategy failureStrategy;

private WarningType warningType;
Expand Down Expand Up @@ -83,6 +89,8 @@ public class ScheduleVO {
public ScheduleVO(Schedule schedule) {
this.setId(schedule.getId());
this.setCrontab(schedule.getCrontab());
this.setTriggerType(schedule.getTriggerType());
this.setMisfirePolicy(schedule.getMisfirePolicy());
this.setProjectName(schedule.getProjectName());
this.setUserName(schedule.getUserName());
this.setWorkerGroup(schedule.getWorkerGroup());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.common.enums;

import lombok.Getter;

import com.baomidou.mybatisplus.annotation.EnumValue;

@Getter
public enum MisfirePolicy {

DO_NOTHING(0),
FIRE_AND_PROCEED(1),
IGNORE_MISFIRES(2);

@EnumValue
private final int code;

MisfirePolicy(int code) {
this.code = code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.common.enums;

import lombok.Getter;

import com.baomidou.mybatisplus.annotation.EnumValue;

@Getter
public enum ScheduleTriggerType {

CRON(0, "Cron expression"),
INTERVAL(1, "Fixed interval");

@EnumValue
private final int code;

private final String description;

ScheduleTriggerType(int code, String description) {
this.code = code;
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.common.utils;

import lombok.Value;

import com.fasterxml.jackson.databind.node.ObjectNode;

/**
* Fixed interval schedule encoded as JSON.
*/
@Value
public class IntervalSchedule {

long intervalMilliseconds;

int repeatCount;

public static IntervalSchedule parse(String expression) {
final ObjectNode values;
try {
values = JSONUtils.parseObject(expression);
} catch (Exception e) {
throw new IllegalArgumentException("Interval schedule expression must be a JSON object", e);
}
if (values == null || !values.isObject()) {
throw new IllegalArgumentException("Interval schedule expression must not be null");
}

int hours = valueOf(values, "hour");
int minutes = valueOf(values, "minute");
int seconds = valueOf(values, "second");
int repeat = valueOf(values, "repeat");
if (hours < 0 || minutes < 0 || seconds < 0 || repeat < -1) {
throw new IllegalArgumentException("Interval duration values must not be negative");
}

long intervalMilliseconds = Math.addExact(
Math.addExact(Math.multiplyExact(hours, 3_600_000L), Math.multiplyExact(minutes, 60_000L)),
Math.multiplyExact(seconds, 1_000L));
if (intervalMilliseconds == 0) {
throw new IllegalArgumentException("Interval duration must be positive");
}
return new IntervalSchedule(intervalMilliseconds, repeat);
}

private static int valueOf(ObjectNode values, String key) {
if (!values.has(key)) {
return 0;
}
if (!values.get(key).isInt()) {
throw new IllegalArgumentException("Interval schedule field must be an integer: " + key);
}
return values.get(key).intValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.common.utils;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

class IntervalScheduleTest {

@Test
void parseIntervalSchedule() {
IntervalSchedule intervalSchedule =
IntervalSchedule.parse("{\"hour\":1,\"minute\":2,\"second\":3,\"repeat\":4}");

assertEquals(3_723_000L, intervalSchedule.getIntervalMilliseconds());
assertEquals(4, intervalSchedule.getRepeatCount());
}

@Test
void rejectInvalidIntervalSchedule() {
assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":0}"));
assertThrows(IllegalArgumentException.class, () -> IntervalSchedule.parse("{\"second\":-1}"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package org.apache.dolphinscheduler.dao.entity;

import org.apache.dolphinscheduler.common.enums.FailureStrategy;
import org.apache.dolphinscheduler.common.enums.MisfirePolicy;
import org.apache.dolphinscheduler.common.enums.Priority;
import org.apache.dolphinscheduler.common.enums.ReleaseState;
import org.apache.dolphinscheduler.common.enums.ScheduleTriggerType;
import org.apache.dolphinscheduler.common.enums.WarningType;

import java.util.Date;
Expand Down Expand Up @@ -67,6 +69,10 @@ public class Schedule {

private String crontab;

private MisfirePolicy misfirePolicy;

private ScheduleTriggerType triggerType;

private FailureStrategy failureStrategy;

private WarningType warningType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.apache.dolphinscheduler.dao.mapper.ScheduleMapper">
<sql id="baseSql">
id, workflow_definition_code, start_time, end_time, timezone_id, crontab, failure_strategy, user_id, release_state,
id, workflow_definition_code, start_time, end_time, timezone_id, crontab, misfire_policy, trigger_type, failure_strategy, user_id, release_state,
warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time
</sql>
<sql id="baseSqlV2">
${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id,
${alias}.crontab, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type,
${alias}.crontab, ${alias}.misfire_policy, ${alias}.trigger_type, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type,
${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time,
${alias}.update_time
</sql>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,8 @@ CREATE TABLE t_ds_schedules
end_time datetime NOT NULL,
timezone_id varchar(40) DEFAULT NULL,
crontab varchar(255) NOT NULL,
misfire_policy tinyint NOT NULL DEFAULT 2,
trigger_type tinyint NOT NULL DEFAULT 0,
failure_strategy tinyint(4) NOT NULL,
user_id int(11) NOT NULL,
release_state tinyint(4) NOT NULL,
Expand Down
Loading
Loading