From 3a82410ddd5d0506142669ddd2aaa129788066ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 25 May 2026 20:26:16 +0800 Subject: [PATCH 01/27] Remove the default workerGroup from the frontend and add backend validation for workerGroup --- .../dolphinscheduler/api/enums/Status.java | 3 + .../ProjectWorkerGroupRelationService.java | 19 +++ .../impl/ProjectPreferenceServiceImpl.java | 38 +++++ ...ProjectWorkerGroupRelationServiceImpl.java | 41 +++++ .../service/impl/SchedulerServiceImpl.java | 20 +++ .../impl/WorkflowDefinitionServiceImpl.java | 29 ++++ .../WorkerGroupValidationContext.java | 43 +++++ .../api/validator/WorkerGroupValidator.java | 53 +++++++ .../workflow/TriggerWorkflowDTOValidator.java | 15 +- .../validator/WorkerGroupValidatorTest.java | 149 ++++++++++++++++++ .../src/views/projects/preference/use-form.ts | 2 +- .../node/tasks/use-aliyun-serverless-spark.ts | 2 +- .../task/components/node/tasks/use-chunjun.ts | 2 +- .../components/node/tasks/use-conditions.ts | 2 +- .../components/node/tasks/use-data-factory.ts | 2 +- .../components/node/tasks/use-datasync.ts | 2 +- .../task/components/node/tasks/use-datax.ts | 2 +- .../components/node/tasks/use-dependent.ts | 2 +- .../task/components/node/tasks/use-dinky.ts | 2 +- .../task/components/node/tasks/use-dms.ts | 2 +- .../task/components/node/tasks/use-dvc.ts | 2 +- .../node/tasks/use-emr-serverless.ts | 2 +- .../task/components/node/tasks/use-emr.ts | 2 +- .../components/node/tasks/use-flink-stream.ts | 2 +- .../task/components/node/tasks/use-flink.ts | 2 +- .../task/components/node/tasks/use-grpc.ts | 2 +- .../components/node/tasks/use-hive-cli.ts | 2 +- .../task/components/node/tasks/use-http.ts | 2 +- .../task/components/node/tasks/use-java.ts | 2 +- .../task/components/node/tasks/use-jupyter.ts | 2 +- .../task/components/node/tasks/use-k8s.ts | 2 +- .../components/node/tasks/use-kubeflow.ts | 2 +- .../task/components/node/tasks/use-linkis.ts | 2 +- .../task/components/node/tasks/use-mlflow.ts | 2 +- .../task/components/node/tasks/use-mr.ts | 2 +- .../components/node/tasks/use-openmldb.ts | 2 +- .../components/node/tasks/use-procedure.ts | 2 +- .../task/components/node/tasks/use-python.ts | 2 +- .../components/node/tasks/use-remote-shell.ts | 2 +- .../components/node/tasks/use-sagemaker.ts | 2 +- .../components/node/tasks/use-sea-tunnel.ts | 2 +- .../task/components/node/tasks/use-shell.ts | 2 +- .../task/components/node/tasks/use-spark.ts | 2 +- .../task/components/node/tasks/use-sql.ts | 2 +- .../task/components/node/tasks/use-sqoop.ts | 2 +- .../components/node/tasks/use-sub-workflow.ts | 2 +- .../task/components/node/tasks/use-switch.ts | 2 +- .../components/node/tasks/use-zeppelin.ts | 2 +- .../projects/task/components/node/types.ts | 2 +- .../definition/components/use-form.ts | 4 +- 50 files changed, 450 insertions(+), 42 deletions(-) create mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java create mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java create mode 100644 dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java index 3d24834bd429..9f4e93456d58 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java @@ -586,6 +586,9 @@ public enum Status { USED_WORKER_GROUP_EXISTS(1402004, "You can not reassign worker groups to the project, cause these worker groups {0} are already used.", "Worker组{0}被项目中任务或定时引用,无法重新分配"), + WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT(1402005, + "Worker group [{0}] is not assigned to the project.", + "Worker组[{0}]未分配给项目"), CREATE_WORKFLOW_LINEAGE_ERROR(1403001, "create workflow lineage error", "创建工作流血缘错误"), UPDATE_WORKFLOW_LINEAGE_ERROR(1403002, "update workflow lineage error", "更新工作流血缘错误"), DELETE_WORKFLOW_LINEAGE_ERROR(1403003, "delete workflow lineage error", "删除工作流血缘错误"), diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java index 19f18d9a3bc3..82c46cc64257 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java @@ -17,6 +17,7 @@ package org.apache.dolphinscheduler.api.service; +import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.dao.entity.ProjectWorkerGroup; import org.apache.dolphinscheduler.dao.entity.User; @@ -43,4 +44,22 @@ public interface ProjectWorkerGroupRelationService { */ List queryAssignedWorkerGroupsByProject(User loginUser, Long projectCode); + /** + * check if worker group is assigned to project + * + * @param projectCode project code + * @param workerGroup worker group name + * @return true if worker group is assigned to project + */ + boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGroup); + + /** + * validate worker groups are all assigned to project + * + * @param projectCode project code + * @param workerGroups worker group names to validate + * @throws ServiceException if any worker group is not assigned + */ + void validateWorkerGroupsAssignedToProject(Long projectCode, List workerGroups); + } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java index 299af216c2e4..fe1b24381d3c 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java @@ -20,17 +20,24 @@ import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT; import org.apache.dolphinscheduler.api.enums.Status; +import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectPreferenceService; import org.apache.dolphinscheduler.api.service.ProjectService; import org.apache.dolphinscheduler.api.utils.Result; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidationContext; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.utils.CodeGenerateUtils; +import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.ProjectPreference; import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.mapper.ProjectPreferenceMapper; import org.apache.dolphinscheduler.dao.repository.ProjectDao; +import org.apache.commons.lang3.StringUtils; + import java.util.Date; +import java.util.Map; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -39,6 +46,7 @@ import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.core.type.TypeReference; @Service @Slf4j @@ -55,6 +63,9 @@ public class ProjectPreferenceServiceImpl extends BaseServiceImpl @Autowired private ProjectDao projectDao; + @Autowired + private WorkerGroupValidator workerGroupValidator; + @Override public Result updateProjectPreference(User loginUser, long projectCode, String preferences) { Result result = new Result(); @@ -67,6 +78,33 @@ public Result updateProjectPreference(User loginUser, long projectCode, String p .selectOne(new QueryWrapper().lambda().eq(ProjectPreference::getProjectCode, projectCode)); + // Validate workerGroup is assigned to project + if (StringUtils.isNotEmpty(preferences)) { + try { + Map preferenceMap = + JSONUtils.parseObject(preferences, new TypeReference>() { + }); + if (preferenceMap != null) { + Object workerGroupObj = preferenceMap.get("workerGroup"); + if (workerGroupObj != null) { + String workerGroup = String.valueOf(workerGroupObj); + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(projectCode) + .build(); + try { + workerGroupValidator.validate(workerGroupContext); + } catch (ServiceException e) { + putMsg(result, Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); + return result; + } + } + } + } catch (Exception e) { + log.warn("Failed to parse preferences JSON: {}", preferences, e); + } + } + Date now = new Date(); if (Objects.isNull(projectPreference)) { projectPreference = new ProjectPreference(); diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java index a43a1efd5709..cd1ce7151027 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java @@ -208,6 +208,24 @@ public List queryAssignedWorkerGroupsByProject(User loginUse }).distinct().collect(Collectors.toList()); } + /** + * Get all assigned worker group names for a project (internal method, no auth check) + */ + private Set getAllAssignedWorkerGroupNames(Long projectCode) { + Project project = projectDao.queryByCode(projectCode); + Set assignedWorkerGroups = new TreeSet<>(); + + if (project != null) { + assignedWorkerGroups.addAll(getAllUsedWorkerGroups(project)); + } + + Set directlyAssignedGroups = + projectWorkerGroupDao.queryAssignedWorkerGroupNamesByProjectCode(projectCode); + assignedWorkerGroups.addAll(directlyAssignedGroups); + + return assignedWorkerGroups; + } + private Set getAllUsedWorkerGroups(Project project) { Set usedWorkerGroups = new TreeSet<>(); // query all worker groups that tasks depend on @@ -226,4 +244,27 @@ private Set getAllUsedWorkerGroups(Project project) { return usedWorkerGroups; } + @Override + public boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGroup) { + if (StringUtils.isEmpty(workerGroup)) { + return true; + } + return getAllAssignedWorkerGroupNames(projectCode).contains(workerGroup); + } + + @Override + public void validateWorkerGroupsAssignedToProject(Long projectCode, List workerGroups) { + if (CollectionUtils.isEmpty(workerGroups)) { + return; + } + + List notAssignedWorkerGroups = workerGroups.stream() + .filter(workerGroup -> !isWorkerGroupAssignedToProject(projectCode, workerGroup)) + .collect(Collectors.toList()); + + if (!notAssignedWorkerGroups.isEmpty()) { + throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, notAssignedWorkerGroups.toString()); + } + } + } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 12df89e9fccf..9f3043fcaccf 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -29,6 +29,8 @@ import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.validator.TenantExistValidator; +import org.apache.dolphinscheduler.api.validator.workflow.WorkerGroupValidationContext; +import org.apache.dolphinscheduler.api.validator.workflow.WorkerGroupValidator; import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; @@ -97,6 +99,9 @@ public class SchedulerServiceImpl extends BaseServiceImpl implements SchedulerSe @Autowired private TenantExistValidator tenantExistValidator; + @Autowired + private WorkerGroupValidator workerGroupValidator; + /** * save schedule * @@ -184,6 +189,14 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setWorkflowInstancePriority(workflowInstancePriority); scheduleObj.setWorkerGroup(workerGroup); scheduleObj.setEnvironmentCode(environmentCode); + + // Validate workerGroup + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(projectCode) + .build(); + workerGroupValidator.validate(workerGroupContext); + scheduleDao.insert(scheduleObj); /** @@ -258,6 +271,13 @@ public Schedule updateSchedule(User loginUser, String.valueOf(schedule.getWorkflowDefinitionCode())); } + // Validate workerGroup + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(projectCode) + .build(); + workerGroupValidator.validate(workerGroupContext); + return updateSchedule(schedule, workflowDefinition, scheduleExpression, warningType, warningGroupId, failureStrategy, workflowInstancePriority, workerGroup, tenantCode, environmentCode); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java index fecf8ec40641..e74201a0d8a5 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java @@ -41,6 +41,7 @@ import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectService; +import org.apache.dolphinscheduler.api.service.ProjectWorkerGroupRelationService; import org.apache.dolphinscheduler.api.service.SchedulerService; import org.apache.dolphinscheduler.api.service.TaskDefinitionLogService; import org.apache.dolphinscheduler.api.service.TaskDefinitionService; @@ -209,6 +210,9 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl implements Wo @Autowired private GlobalParamsValidator globalParamsValidator; + @Autowired + private ProjectWorkerGroupRelationService projectWorkerGroupRelationService; + /** * create workflow definition * @@ -256,6 +260,10 @@ public WorkflowDefinition createWorkflowDefinition(User loginUser, globalParamsValidator.validate(globalParams); List taskDefinitionLogs = generateTaskDefinitionList(taskDefinitionJson); + + // Validate worker groups in task definitions + validateTaskWorkerGroups(projectCode, taskDefinitionLogs); + List taskRelationList = generateTaskRelationList(taskRelationJson, taskDefinitionLogs); long workflowDefinitionCode = CodeGenerateUtils.genCode(); @@ -381,6 +389,23 @@ private List generateTaskDefinitionList(String taskDefinition } } + /** + * Validate worker groups in task definitions + */ + private void validateTaskWorkerGroups(long projectCode, List taskDefinitionLogs) { + if (CollectionUtils.isEmpty(taskDefinitionLogs)) { + return; + } + + List workerGroups = taskDefinitionLogs.stream() + .map(TaskDefinitionLog::getWorkerGroup) + .filter(StringUtils::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + + projectWorkerGroupRelationService.validateWorkerGroupsAssignedToProject(projectCode, workerGroups); + } + private List generateTaskRelationList(String taskRelationJson, List taskDefinitionLogs) { try { @@ -626,6 +651,10 @@ public WorkflowDefinition updateWorkflowDefinition(User loginUser, globalParamsValidator.validate(globalParams); List taskDefinitionLogs = generateTaskDefinitionList(taskDefinitionJson); + + // Validate worker groups in task definitions + validateTaskWorkerGroups(projectCode, taskDefinitionLogs); + List taskRelationList = generateTaskRelationList(taskRelationJson, taskDefinitionLogs); WorkflowDefinition workflowDefinition = workflowDefinitionDao.queryByCode(code).orElse(null); diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java new file mode 100644 index 000000000000..d1e6368f50d6 --- /dev/null +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java @@ -0,0 +1,43 @@ +/* + * 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.validator; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Validation context for workerGroup validation + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkerGroupValidationContext { + + /** + * The workerGroup to validate + */ + private String workerGroup; + + /** + * The project code to check against + */ + private long projectCode; +} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java new file mode 100644 index 000000000000..c4f9f5f6124a --- /dev/null +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java @@ -0,0 +1,53 @@ +/* + * 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.validator; + +import org.apache.dolphinscheduler.api.enums.Status; +import org.apache.dolphinscheduler.api.exceptions.ServiceException; +import org.apache.dolphinscheduler.api.service.ProjectWorkerGroupRelationService; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Validator for workerGroup validation + * Checks if the workerGroup is assigned to the project + */ +@Slf4j +@Component +public class WorkerGroupValidator implements IValidator { + + @Autowired + private ProjectWorkerGroupRelationService projectWorkerGroupRelationService; + + @Override + public void validate(final WorkerGroupValidationContext context) { + String workerGroup = context.getWorkerGroup(); + long projectCode = context.getProjectCode(); + + if (StringUtils.isNotEmpty(workerGroup) + && !projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, workerGroup)) { + log.warn("Worker group {} is not assigned to project {}", workerGroup, projectCode); + throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); + } + } +} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java index 4837beec2a49..3b74aaa99b06 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java @@ -20,6 +20,8 @@ import org.apache.dolphinscheduler.api.validator.IValidator; import org.apache.dolphinscheduler.api.validator.StartParamListValidator; import org.apache.dolphinscheduler.api.validator.TenantExistValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidationContext; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.enums.CommandType; import org.apache.dolphinscheduler.common.enums.ReleaseState; @@ -35,10 +37,14 @@ public class TriggerWorkflowDTOValidator implements IValidator workerGroupValidator.validate(context)) + .doesNotThrowAnyException(); + } + + @Test + void testValidate_emptyWorkerGroup() { + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup("") + .projectCode(PROJECT_CODE) + .build(); + + assertThatCode(() -> workerGroupValidator.validate(context)) + .doesNotThrowAnyException(); + } + + @Test + void testValidate_blankWorkerGroup() { + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(" ") + .projectCode(PROJECT_CODE) + .build(); + + assertThatCode(() -> workerGroupValidator.validate(context)) + .doesNotThrowAnyException(); + } + + @Test + void testValidate_validWorkerGroup() { + String validWorkerGroup = "default"; + when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, validWorkerGroup)) + .thenReturn(true); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(validWorkerGroup) + .projectCode(PROJECT_CODE) + .build(); + + assertThatCode(() -> workerGroupValidator.validate(context)) + .doesNotThrowAnyException(); + } + + @Test + void testValidate_invalidWorkerGroup() { + String invalidWorkerGroup = "invalid-group"; + when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, invalidWorkerGroup)) + .thenReturn(false); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(invalidWorkerGroup) + .projectCode(PROJECT_CODE) + .build(); + + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining(invalidWorkerGroup); + } + + @Test + void testValidate_differentProjectCode() { + long anotherProjectCode = 2L; + String workerGroup = "default"; + + when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, workerGroup)) + .thenReturn(true); + when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(anotherProjectCode, workerGroup)) + .thenReturn(false); + + // Should pass for PROJECT_CODE + WorkerGroupValidationContext validContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + assertThatCode(() -> workerGroupValidator.validate(validContext)) + .doesNotThrowAnyException(); + + // Should fail for anotherProjectCode + WorkerGroupValidationContext invalidContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(anotherProjectCode) + .build(); + assertThatThrownBy(() -> workerGroupValidator.validate(invalidContext)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testValidate_caseSensitive() { + String workerGroup = "Default"; + when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, workerGroup)) + .thenReturn(false); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); + } +} diff --git a/dolphinscheduler-ui/src/views/projects/preference/use-form.ts b/dolphinscheduler-ui/src/views/projects/preference/use-form.ts index d7a97f509dfa..9776d5a7cb78 100644 --- a/dolphinscheduler-ui/src/views/projects/preference/use-form.ts +++ b/dolphinscheduler-ui/src/views/projects/preference/use-form.ts @@ -59,7 +59,7 @@ export function useForm() { const data = reactive({ model: { taskPriority: 'MEDIUM', - workerGroup: 'default', + workerGroup: null, environmentCode: null, failRetryTimes: 0, failRetryInterval: 1, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-aliyun-serverless-spark.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-aliyun-serverless-spark.ts index 6ffeb5ea7c11..f72448e5f0e0 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-aliyun-serverless-spark.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-aliyun-serverless-spark.ts @@ -40,7 +40,7 @@ export function useAliyunServerlessSpark({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: 'ALIYUN_SERVERLESS_SPARK', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-chunjun.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-chunjun.ts index 2223e0b3e207..02f8ed6353f1 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-chunjun.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-chunjun.ts @@ -42,7 +42,7 @@ export function useChunjun({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, customConfig: true, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-conditions.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-conditions.ts index 08e325099fd0..70b38461a8c9 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-conditions.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-conditions.ts @@ -40,7 +40,7 @@ export function useConditions({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, timeout: 30, relation: 'AND', dependTaskList: [], diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-data-factory.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-data-factory.ts index 1ea010a4c20e..0d6baed99d46 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-data-factory.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-data-factory.ts @@ -41,7 +41,7 @@ export function useDataFactory({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'], diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datasync.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datasync.ts index f46ac6bef961..c74f8b348cc1 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datasync.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datasync.ts @@ -40,7 +40,7 @@ export function useDatasync({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datax.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datax.ts index 0304f06a7516..92771d0efc68 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datax.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-datax.ts @@ -41,7 +41,7 @@ export function useDataX({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dependent.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dependent.ts index b140eea7f3d0..c9a1cb754391 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dependent.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dependent.ts @@ -40,7 +40,7 @@ export function useDependent({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, relation: 'AND', dependTaskList: [], diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dinky.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dinky.ts index 080ea473ada8..d929ba856f02 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dinky.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dinky.ts @@ -40,7 +40,7 @@ export function useDinky({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dms.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dms.ts index 28726b37190d..33e0d3bca301 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dms.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dms.ts @@ -40,7 +40,7 @@ export function useDms({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'], diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dvc.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dvc.ts index eeeb9618c9ef..1ddab634c4a5 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dvc.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-dvc.ts @@ -40,7 +40,7 @@ export function useDvc({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'], diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr-serverless.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr-serverless.ts index 1b3408262718..6423f911c4db 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr-serverless.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr-serverless.ts @@ -41,7 +41,7 @@ export function useEmrServerless({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, applicationId: '', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr.ts index 00fb55aa7a30..841df1f0d7af 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-emr.ts @@ -41,7 +41,7 @@ export function useEmr({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, programType: 'ADD_JOB_FLOW_STEPS', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink-stream.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink-stream.ts index 3b70abff3048..a69ccb4bd223 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink-stream.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink-stream.ts @@ -40,7 +40,7 @@ export function useFlinkStream({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, programType: 'SCALA', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink.ts index 9e956eb0ff18..85308a81c08c 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-flink.ts @@ -40,7 +40,7 @@ export function useFlink({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, programType: 'SCALA', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-grpc.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-grpc.ts index af2b856809d1..50e8c3beb504 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-grpc.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-grpc.ts @@ -40,7 +40,7 @@ export function useGrpc({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, grpcCredentialType: 'INSECURE', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-hive-cli.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-hive-cli.ts index cbe5bd7988a3..a9c5fd58ae74 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-hive-cli.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-hive-cli.ts @@ -40,7 +40,7 @@ export function useHiveCli({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, hiveCliTaskExecutionType: 'SCRIPT' diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-http.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-http.ts index eeb42c51e0c9..73a0c7cf716a 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-http.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-http.ts @@ -40,7 +40,7 @@ export function useHttp({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, httpMethod: 'GET', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-java.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-java.ts index 8671e6e79a75..a948a2458dd0 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-java.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-java.ts @@ -40,7 +40,7 @@ export function useJava({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, isModulePath: false, rawScript: '', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-jupyter.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-jupyter.ts index 21ca400ac51d..04c1f52eff3c 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-jupyter.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-jupyter.ts @@ -40,7 +40,7 @@ export function useJupyter({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-k8s.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-k8s.ts index e5ce9dad1ad7..faa059e1e75c 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-k8s.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-k8s.ts @@ -43,7 +43,7 @@ export function useK8s({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: 'K8S', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-kubeflow.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-kubeflow.ts index a9c82bea6a3f..4cea28d9abbc 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-kubeflow.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-kubeflow.ts @@ -40,7 +40,7 @@ export function useKubeflow({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-linkis.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-linkis.ts index 4d022030327c..cc0487833fe2 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-linkis.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-linkis.ts @@ -41,7 +41,7 @@ export function useLinkis({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts index b68c910a0c91..4e3cdc4de7cb 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mlflow.ts @@ -40,7 +40,7 @@ export function useMlflow({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, algorithm: 'svm', mlflowTrackingUri: 'http://127.0.0.1:5000', mlflowTaskType: 'MLflow Projects', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mr.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mr.ts index ade369889586..7fc496275857 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mr.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-mr.ts @@ -40,7 +40,7 @@ export function useMr({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, programType: 'SCALA', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-openmldb.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-openmldb.ts index f878ce8f0c59..11643f4dc6ae 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-openmldb.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-openmldb.ts @@ -42,7 +42,7 @@ export function useOpenmldb({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, zk: '', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-procedure.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-procedure.ts index 2a0def32d55a..ea9d2d3af807 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-procedure.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-procedure.ts @@ -41,7 +41,7 @@ export function useProcedure({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: data?.taskParams?.type ? data?.taskParams?.type : 'MYSQL', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-python.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-python.ts index d58a13201076..0593d90366d6 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-python.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-python.ts @@ -41,7 +41,7 @@ export function usePython({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-remote-shell.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-remote-shell.ts index 1340ee457276..6bdf75c364e1 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-remote-shell.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-remote-shell.ts @@ -42,7 +42,7 @@ export function useRemoteShell({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, type: 'SSH', rawScript: '' diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sagemaker.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sagemaker.ts index 3e7d6ff5d75b..86a71c1d213d 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sagemaker.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sagemaker.ts @@ -40,7 +40,7 @@ export function userSagemaker({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: 'SAGEMAKER', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sea-tunnel.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sea-tunnel.ts index 6df07f71f215..3890949cb1b2 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sea-tunnel.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sea-tunnel.ts @@ -41,7 +41,7 @@ export function useSeaTunnel({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-shell.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-shell.ts index a90ab59dd8ef..4f787dc69734 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-shell.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-shell.ts @@ -42,7 +42,7 @@ export function useShell({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-spark.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-spark.ts index 883950ae516f..488f54851435 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-spark.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-spark.ts @@ -40,7 +40,7 @@ export function useSpark({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, programType: 'SCALA', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts index 371ae737fa7d..160de9231bce 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sql.ts @@ -41,7 +41,7 @@ export function useSql({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: 'MYSQL', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sqoop.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sqoop.ts index 879b06858fc6..3e97d6d86743 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sqoop.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sqoop.ts @@ -40,7 +40,7 @@ export function useSqoop({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, cpuQuota: -1, memoryMax: -1, delayTime: 0, diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sub-workflow.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sub-workflow.ts index 540460700cc8..30bf862bba4c 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sub-workflow.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-sub-workflow.ts @@ -43,7 +43,7 @@ export function useSubWorkflow({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, timeoutNotifyStrategy: ['WARN'] diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-switch.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-switch.ts index 1e6d3f80cc34..2c0ee5963cd9 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-switch.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-switch.ts @@ -40,7 +40,7 @@ export function useSwitch({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, rawScript: '', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-zeppelin.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-zeppelin.ts index 8c072549a937..e5dc4efb3f16 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-zeppelin.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/tasks/use-zeppelin.ts @@ -40,7 +40,7 @@ export function useZeppelin({ environmentCode: null, failRetryInterval: 1, failRetryTimes: 0, - workerGroup: 'default', + workerGroup: null, delayTime: 0, timeout: 30, type: 'ZEPPELIN', diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts index 218f318db0d0..0f7c57917a6f 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/types.ts @@ -511,7 +511,7 @@ interface INodeData timeout?: number timeoutFlag?: boolean timeoutNotifyStrategy?: string[] - workerGroup?: string + workerGroup?: string | null code?: number name?: string preTasks?: number[] diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts index f4955a9d990d..11ab72047f12 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts @@ -62,7 +62,7 @@ export const useForm = () => { complementDependentMode: 'OFF_MODE', runMode: 'RUN_MODE_SERIAL', workflowInstancePriority: 'MEDIUM', - workerGroup: 'default', + workerGroup: null, tenantCode: 'default', environmentCode: null, startParams: null, @@ -140,7 +140,7 @@ export const useForm = () => { warningType: 'NONE', workflowInstancePriority: 'MEDIUM', warningGroupId: null as null | number, - workerGroup: 'default', + workerGroup: null, tenantCode: 'default', environmentCode: null as null | string }, From 1d4e03d3b28e6eaa3c479a7242787e74e74129ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 25 May 2026 22:14:45 +0800 Subject: [PATCH 02/27] update import --- .../service/impl/SchedulerServiceImpl.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 9f3043fcaccf..83bd6cef9c98 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -29,8 +29,8 @@ import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.validator.TenantExistValidator; -import org.apache.dolphinscheduler.api.validator.workflow.WorkerGroupValidationContext; -import org.apache.dolphinscheduler.api.validator.workflow.WorkerGroupValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidationContext; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; @@ -187,8 +187,6 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setUserName(loginUser.getUserName()); scheduleObj.setReleaseState(ReleaseState.OFFLINE); scheduleObj.setWorkflowInstancePriority(workflowInstancePriority); - scheduleObj.setWorkerGroup(workerGroup); - scheduleObj.setEnvironmentCode(environmentCode); // Validate workerGroup WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() @@ -196,7 +194,8 @@ public Schedule insertSchedule(User loginUser, .projectCode(projectCode) .build(); workerGroupValidator.validate(workerGroupContext); - + scheduleObj.setWorkerGroup(workerGroup); + scheduleObj.setEnvironmentCode(environmentCode); scheduleDao.insert(scheduleObj); /** @@ -271,13 +270,6 @@ public Schedule updateSchedule(User loginUser, String.valueOf(schedule.getWorkflowDefinitionCode())); } - // Validate workerGroup - WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() - .workerGroup(workerGroup) - .projectCode(projectCode) - .build(); - workerGroupValidator.validate(workerGroupContext); - return updateSchedule(schedule, workflowDefinition, scheduleExpression, warningType, warningGroupId, failureStrategy, workflowInstancePriority, workerGroup, tenantCode, environmentCode); } @@ -590,6 +582,12 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setFailureStrategy(failureStrategy); } + // Validate workerGroup + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(workflowDefinition.getProjectCode()) + .build(); + workerGroupValidator.validate(workerGroupContext); schedule.setWorkerGroup(workerGroup); schedule.setEnvironmentCode(environmentCode); schedule.setUpdateTime(now); From a497475377a44ea4e57697bd16ee53e1ff3bed74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Wed, 27 May 2026 10:43:46 +0800 Subject: [PATCH 03/27] Optimize some issues. --- .../api/dto/ProjectPreferencesDTO.java | 50 +++++++++++ .../ProjectWorkerGroupRelationService.java | 10 +-- .../impl/ProjectPreferenceServiceImpl.java | 33 +++---- ...ProjectWorkerGroupRelationServiceImpl.java | 21 +---- .../service/impl/SchedulerServiceImpl.java | 2 - .../impl/WorkflowDefinitionServiceImpl.java | 9 +- .../WorkerGroupValidationContext.java | 10 +-- .../api/validator/WorkerGroupValidator.java | 47 +++++++++- .../workflow/TriggerWorkflowDTOValidator.java | 1 - .../validator/WorkerGroupValidatorTest.java | 85 +++++++++++++++++++ 10 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ProjectPreferencesDTO.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ProjectPreferencesDTO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ProjectPreferencesDTO.java new file mode 100644 index 000000000000..c6f7f6cdd73d --- /dev/null +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ProjectPreferencesDTO.java @@ -0,0 +1,50 @@ +/* + * 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.dto; + +import lombok.Data; + +@Data +public class ProjectPreferencesDTO { + + private String taskPriority; + + private String workerGroup; + + private Long environmentCode; + + private Integer failRetryTimes; + + private Integer failRetryInterval; + + private Integer cpuQuota; + + private Integer memoryMax; + + private Boolean timeoutFlag; + + private String[] timeoutNotifyStrategy; + + private Integer timeout; + + private String warningType; + + private String tenantCode; + + private Integer warningGroupId; +} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java index 82c46cc64257..fc04aec005db 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationService.java @@ -17,12 +17,12 @@ package org.apache.dolphinscheduler.api.service; -import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.dao.entity.ProjectWorkerGroup; import org.apache.dolphinscheduler.dao.entity.User; import java.util.List; +import java.util.Set; public interface ProjectWorkerGroupRelationService { @@ -54,12 +54,12 @@ public interface ProjectWorkerGroupRelationService { boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGroup); /** - * validate worker groups are all assigned to project + * get all assigned worker group names for a project + * This includes both directly assigned worker groups and worker groups used by tasks/schedules * * @param projectCode project code - * @param workerGroups worker group names to validate - * @throws ServiceException if any worker group is not assigned + * @return set of all assigned worker group names */ - void validateWorkerGroupsAssignedToProject(Long projectCode, List workerGroups); + Set getAllAssignedWorkerGroupNames(Long projectCode); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java index fe1b24381d3c..164df5ade502 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectPreferenceServiceImpl.java @@ -19,6 +19,7 @@ import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT; +import org.apache.dolphinscheduler.api.dto.ProjectPreferencesDTO; import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectPreferenceService; @@ -37,7 +38,6 @@ import org.apache.commons.lang3.StringUtils; import java.util.Date; -import java.util.Map; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -46,7 +46,6 @@ import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.fasterxml.jackson.core.type.TypeReference; @Service @Slf4j @@ -78,30 +77,24 @@ public Result updateProjectPreference(User loginUser, long projectCode, String p .selectOne(new QueryWrapper().lambda().eq(ProjectPreference::getProjectCode, projectCode)); - // Validate workerGroup is assigned to project if (StringUtils.isNotEmpty(preferences)) { try { - Map preferenceMap = - JSONUtils.parseObject(preferences, new TypeReference>() { - }); - if (preferenceMap != null) { - Object workerGroupObj = preferenceMap.get("workerGroup"); - if (workerGroupObj != null) { - String workerGroup = String.valueOf(workerGroupObj); - WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() - .workerGroup(workerGroup) - .projectCode(projectCode) - .build(); - try { - workerGroupValidator.validate(workerGroupContext); - } catch (ServiceException e) { - putMsg(result, Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); - return result; - } + ProjectPreferencesDTO preferencesDTO = JSONUtils.parseObject(preferences, ProjectPreferencesDTO.class); + if (preferencesDTO != null && StringUtils.isNotEmpty(preferencesDTO.getWorkerGroup())) { + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(preferencesDTO.getWorkerGroup()) + .projectCode(projectCode) + .build(); + try { + workerGroupValidator.validate(workerGroupContext); + } catch (ServiceException e) { + putMsg(result, Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, preferencesDTO.getWorkerGroup()); + return result; } } } catch (Exception e) { log.warn("Failed to parse preferences JSON: {}", preferences, e); + throw new ServiceException(Status.UPDATE_PROJECT_PREFERENCE_ERROR); } } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java index cd1ce7151027..d4d0e8575f4f 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java @@ -208,10 +208,8 @@ public List queryAssignedWorkerGroupsByProject(User loginUse }).distinct().collect(Collectors.toList()); } - /** - * Get all assigned worker group names for a project (internal method, no auth check) - */ - private Set getAllAssignedWorkerGroupNames(Long projectCode) { + @Override + public Set getAllAssignedWorkerGroupNames(Long projectCode) { Project project = projectDao.queryByCode(projectCode); Set assignedWorkerGroups = new TreeSet<>(); @@ -252,19 +250,4 @@ public boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGro return getAllAssignedWorkerGroupNames(projectCode).contains(workerGroup); } - @Override - public void validateWorkerGroupsAssignedToProject(Long projectCode, List workerGroups) { - if (CollectionUtils.isEmpty(workerGroups)) { - return; - } - - List notAssignedWorkerGroups = workerGroups.stream() - .filter(workerGroup -> !isWorkerGroupAssignedToProject(projectCode, workerGroup)) - .collect(Collectors.toList()); - - if (!notAssignedWorkerGroups.isEmpty()) { - throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, notAssignedWorkerGroups.toString()); - } - } - } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 83bd6cef9c98..68f1ce73c79e 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -188,7 +188,6 @@ public Schedule insertSchedule(User loginUser, scheduleObj.setReleaseState(ReleaseState.OFFLINE); scheduleObj.setWorkflowInstancePriority(workflowInstancePriority); - // Validate workerGroup WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() .workerGroup(workerGroup) .projectCode(projectCode) @@ -582,7 +581,6 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe schedule.setFailureStrategy(failureStrategy); } - // Validate workerGroup WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() .workerGroup(workerGroup) .projectCode(workflowDefinition.getProjectCode()) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java index e74201a0d8a5..b798e3ed12b4 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java @@ -41,7 +41,6 @@ import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectService; -import org.apache.dolphinscheduler.api.service.ProjectWorkerGroupRelationService; import org.apache.dolphinscheduler.api.service.SchedulerService; import org.apache.dolphinscheduler.api.service.TaskDefinitionLogService; import org.apache.dolphinscheduler.api.service.TaskDefinitionService; @@ -52,6 +51,7 @@ import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.validator.GlobalParamsValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.UserType; @@ -211,7 +211,7 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl implements Wo private GlobalParamsValidator globalParamsValidator; @Autowired - private ProjectWorkerGroupRelationService projectWorkerGroupRelationService; + private WorkerGroupValidator workerGroupValidator; /** * create workflow definition @@ -399,11 +399,9 @@ private void validateTaskWorkerGroups(long projectCode, List List workerGroups = taskDefinitionLogs.stream() .map(TaskDefinitionLog::getWorkerGroup) - .filter(StringUtils::isNotEmpty) - .distinct() .collect(Collectors.toList()); - projectWorkerGroupRelationService.validateWorkerGroupsAssignedToProject(projectCode, workerGroups); + workerGroupValidator.validate(workerGroups, projectCode); } private List generateTaskRelationList(String taskRelationJson, @@ -1665,7 +1663,6 @@ public void switchWorkflowDefinitionVersion(User loginUser, long projectCode, lo * * @param srcProjectCode srcProjectCode * @param targetProjectCode targetProjectCode - * @param result result * @param failedWorkflowList failedWorkflowList * @param isCopy isCopy */ diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java index d1e6368f50d6..0831019d950c 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidationContext.java @@ -22,22 +22,14 @@ import lombok.Data; import lombok.NoArgsConstructor; -/** - * Validation context for workerGroup validation - */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class WorkerGroupValidationContext { - /** - * The workerGroup to validate - */ private String workerGroup; - /** - * The project code to check against - */ private long projectCode; + } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java index c4f9f5f6124a..10667764b790 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java @@ -21,8 +21,13 @@ import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectWorkerGroupRelationService; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -44,10 +49,50 @@ public void validate(final WorkerGroupValidationContext context) { String workerGroup = context.getWorkerGroup(); long projectCode = context.getProjectCode(); - if (StringUtils.isNotEmpty(workerGroup) + if (StringUtils.isNotBlank(workerGroup) && !projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, workerGroup)) { log.warn("Worker group {} is not assigned to project {}", workerGroup, projectCode); throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); } } + + /** + * Validate a list of workerGroups are assigned to the project + * This method queries the assigned workerGroups once and then checks all workerGroups against it + * + * @param workerGroups the list of workerGroups to validate + * @param projectCode the project code + */ + public void validate(final List workerGroups, final long projectCode) { + if (CollectionUtils.isEmpty(workerGroups)) { + return; + } + + List validWorkerGroups = workerGroups.stream() + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(validWorkerGroups)) { + return; + } + + Set assignedWorkerGroupNames = projectWorkerGroupRelationService + .getAllAssignedWorkerGroupNames(projectCode); + + if (assignedWorkerGroupNames == null) { + assignedWorkerGroupNames = new java.util.HashSet<>(); + } + + Set finalAssignedWorkerGroupNames = assignedWorkerGroupNames; + List unassignedWorkerGroups = validWorkerGroups.stream() + .filter(wg -> !finalAssignedWorkerGroupNames.contains(wg)) + .collect(Collectors.toList()); + + if (!unassignedWorkerGroups.isEmpty()) { + log.warn("Worker groups {} are not assigned to project {}", unassignedWorkerGroups, projectCode); + throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, + String.join(",", unassignedWorkerGroups)); + } + } } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java index 3b74aaa99b06..cbd523c941fe 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java @@ -63,7 +63,6 @@ public void validate(final TriggerWorkflowDTO triggerWorkflowDTO) { startParamListValidator.validate(triggerWorkflowDTO.getStartParamList()); - // Validate workerGroup using WorkerGroupValidator WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() .workerGroup(triggerWorkflowDTO.getWorkerGroup()) .projectCode(triggerWorkflowDTO.getWorkflowDefinition().getProjectCode()) diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java index a9f12dfde2cc..b506cf00f3dd 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java @@ -24,6 +24,12 @@ import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.ProjectWorkerGroupRelationService; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -146,4 +152,83 @@ void testValidate_caseSensitive() { assertThatThrownBy(() -> workerGroupValidator.validate(context)) .isInstanceOf(ServiceException.class); } + + @Test + void testBatchValidate_nullList() { + assertThatCode(() -> workerGroupValidator.validate((List) null, PROJECT_CODE)) + .doesNotThrowAnyException(); + } + + @Test + void testBatchValidate_emptyList() { + assertThatCode(() -> workerGroupValidator.validate(Collections.emptyList(), PROJECT_CODE)) + .doesNotThrowAnyException(); + } + + @Test + void testBatchValidate_listWithOnlyEmptyStrings() { + List workerGroups = Arrays.asList("", " ", null); + assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .doesNotThrowAnyException(); + } + + @Test + void testBatchValidate_allValidWorkerGroups() { + List workerGroups = Arrays.asList("default", "group-a", "group-b"); + Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a", "group-b")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .doesNotThrowAnyException(); + } + + @Test + void testBatchValidate_someInvalidWorkerGroups() { + List workerGroups = Arrays.asList("default", "invalid-group"); + Set assignedGroups = new HashSet<>(Collections.singletonList("default")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("invalid-group"); + } + + @Test + void testBatchValidate_allInvalidWorkerGroups() { + List workerGroups = Arrays.asList("invalid-1", "invalid-2"); + Set assignedGroups = new HashSet<>(); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("invalid-1") + .hasMessageContaining("invalid-2"); + } + + @Test + void testBatchValidate_withDuplicates() { + List workerGroups = Arrays.asList("default", "default", "group-a"); + Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + // Should not throw because duplicates are filtered out + assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .doesNotThrowAnyException(); + } + + @Test + void testBatchValidate_withEmptyStrings() { + List workerGroups = Arrays.asList("default", "", "group-a"); + Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + // Should not throw because empty strings are filtered out + assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .doesNotThrowAnyException(); + } } From 1c0c970ee4e4609debda1395ff1c4d59e7231160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 2 Jun 2026 14:29:29 +0800 Subject: [PATCH 04/27] update unit test --- .../test/resources/workflow-json/test.json | 2 +- .../ProjectPreferenceControllerTest.java | 3 ++- .../service/ProjectPreferenceServiceTest.java | 21 ++++++++++++++----- .../WorkflowDefinitionServiceTest.java | 9 ++++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json index 1a160d747422..71385fa9f386 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json @@ -19,7 +19,7 @@ "timeout": 0, "timeoutFlag": "CLOSE", "timeoutNotifyStrategy": "", - "workerGroup": "default", + "workerGroup": "", "cpuQuota": -1, "memoryMax": -1, "taskExecuteType": "BATCH" diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ProjectPreferenceControllerTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ProjectPreferenceControllerTest.java index b2172af26eca..02937164e769 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ProjectPreferenceControllerTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/controller/ProjectPreferenceControllerTest.java @@ -50,7 +50,8 @@ public void testUpdateProjectPreference() { Mockito.when(projectPreferenceService.updateProjectPreference(Mockito.any(), Mockito.anyLong(), Mockito.anyString())).thenReturn(getSuccessResult()); - Result result = projectPreferenceController.updateProjectPreference(loginUser, 1, "value"); + String validPreferences = "{\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\"}"; + Result result = projectPreferenceController.updateProjectPreference(loginUser, 1, validPreferences); Assertions.assertEquals(Status.SUCCESS.getCode(), result.getCode()); } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectPreferenceServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectPreferenceServiceTest.java index 5ee5873790fa..4fbeadbeaedf 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectPreferenceServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectPreferenceServiceTest.java @@ -22,6 +22,7 @@ import org.apache.dolphinscheduler.api.service.impl.ProjectPreferenceServiceImpl; import org.apache.dolphinscheduler.api.service.impl.ProjectServiceImpl; import org.apache.dolphinscheduler.api.utils.Result; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.ProjectPreference; @@ -55,18 +56,28 @@ public class ProjectPreferenceServiceTest { @Mock private ProjectServiceImpl projectService; + @Mock + private WorkerGroupValidator workerGroupValidator; + protected final static long projectCode = 1L; + protected final static String VALID_PREFERENCES = "{\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\"}"; @Test public void testUpdateProjectPreference() { User loginUser = getGeneralUser(); - // no permission + // preferences: no valid json Mockito.doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)) .when(projectService).checkHasProjectWritePermissionThrowException(Mockito.any(), Mockito.any()); Assertions.assertThrows(ServiceException.class, () -> projectPreferenceService.updateProjectPreference(loginUser, projectCode, "value")); + // no permission + Mockito.doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)) + .when(projectService).checkHasProjectWritePermissionThrowException(Mockito.any(), Mockito.any()); + Assertions.assertThrows(ServiceException.class, + () -> projectPreferenceService.updateProjectPreference(loginUser, projectCode, VALID_PREFERENCES)); + // when preference exists in project Mockito.when(projectPreferenceMapper.selectOne(Mockito.any())).thenReturn(null); Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(getProject(projectCode)); @@ -77,12 +88,12 @@ public void testUpdateProjectPreference() { Mockito.when(projectPreferenceMapper.insert(Mockito.any())).thenReturn(1); - Result result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, "value"); + Result result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, VALID_PREFERENCES); Assertions.assertEquals(Status.SUCCESS.getCode(), result.getCode()); // database operatation fail Mockito.when(projectPreferenceMapper.insert(Mockito.any())).thenReturn(-1); - result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, "value"); + result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, VALID_PREFERENCES); Assertions.assertEquals(Status.CREATE_PROJECT_PREFERENCE_ERROR.getCode(), result.getCode()); // when preference exists in project @@ -90,12 +101,12 @@ public void testUpdateProjectPreference() { // success Mockito.when(projectPreferenceMapper.updateById(Mockito.any())).thenReturn(1); - result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, "value"); + result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, VALID_PREFERENCES); Assertions.assertEquals(Status.SUCCESS.getCode(), result.getCode()); // database operation fail Mockito.when(projectPreferenceMapper.updateById(Mockito.any())).thenReturn(-1); - result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, "value"); + result = projectPreferenceService.updateProjectPreference(loginUser, projectCode, VALID_PREFERENCES); Assertions.assertEquals(Status.UPDATE_PROJECT_PREFERENCE_ERROR.getCode(), result.getCode()); } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java index 3ea6da8fb3a9..212dab0dd61f 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java @@ -41,12 +41,12 @@ import org.apache.dolphinscheduler.api.service.impl.WorkflowDefinitionServiceImpl; import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.validator.GlobalParamsValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.UserType; -import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.enums.WorkflowExecutionTypeEnum; import org.apache.dolphinscheduler.common.graph.DAG; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -197,6 +197,9 @@ public class WorkflowDefinitionServiceTest extends BaseServiceTestTool { @Mock private GlobalParamsValidator globalParamsValidator; + @Mock + private WorkerGroupValidator workerGroupValidator; + @Mock private UserDao userDao; @@ -836,6 +839,7 @@ public void testCreateWorkflowDefinitionShouldSyncVersionToResponse() { eq(Boolean.TRUE))).thenReturn(1); when(processService.saveTaskRelation(eq(user), eq(projectCode), anyLong(), eq(1), anyList(), anyList(), eq(Boolean.TRUE))).thenReturn(Constants.EXIT_CODE_SUCCESS); + doNothing().when(workerGroupValidator).validate(anyList(), anyLong()); WorkflowDefinition workflowDefinition = workflowDefinitionService.createWorkflowDefinition( user, projectCode, name, description, "[]", "[]", timeout, @@ -862,6 +866,7 @@ public void testUpdateWorkflowDefinitionShouldSyncVersionToResponse() { .thenReturn(Collections.emptyList()); when(processService.saveTaskRelation(eq(user), eq(projectCode), eq(processDefinitionCode), eq(2), anyList(), anyList(), eq(Boolean.TRUE))).thenReturn(Constants.EXIT_CODE_SUCCESS); + doNothing().when(workerGroupValidator).validate(anyList(), anyLong()); WorkflowDefinition resultDefinition = workflowDefinitionService.updateWorkflowDefinition( user, projectCode, name, processDefinitionCode, description, "[]", "[]", timeout, @@ -990,7 +995,7 @@ private Schedule getSchedule() { schedule.setUserId(1); schedule.setReleaseState(ReleaseState.OFFLINE); schedule.setWorkflowInstancePriority(Priority.MEDIUM); - schedule.setWarningType(WarningType.NONE); + schedule.setWarningType(null); schedule.setWarningGroupId(1); schedule.setWorkerGroup(WorkerGroupUtils.getDefaultWorkerGroup()); return schedule; From 1a4e69fe400be824e3e5f081168b61864cc030de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 16 Jun 2026 17:33:25 +0800 Subject: [PATCH 05/27] update test --- .../api/test/cases/SchedulerAPITest.java | 5 +++++ .../api/test/pages/project/ProjectPage.java | 13 +++++++++++++ .../src/test/resources/workflow-json/test.json | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java index 2919b0a66f08..a44a416f7b4e 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/SchedulerAPITest.java @@ -98,6 +98,11 @@ public void testCreateSchedule() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + + String[] workerGroups = {"default"}; + HttpResponse assignWorkerGroupsResponse = projectPage.assignWorkerGroups(loginUser, projectCode, workerGroups); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); + ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); workflowDefinitionPage.createWorkflowDefinition(loginUser, projectCode, file, workflowDefinitionName); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/project/ProjectPage.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/project/ProjectPage.java index 3a9aaa079db5..3778a7bc2448 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/project/ProjectPage.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/project/ProjectPage.java @@ -177,4 +177,17 @@ public HttpResponse deleteProject(User loginUser, Long code) { String url = String.format("/projects/%d", code); return requestClient.delete(url, headers, params); } + + public HttpResponse assignWorkerGroups(User loginUser, long projectCode, String[] workerGroups) { + Map params = new HashMap<>(); + params.put("loginUser", loginUser); + params.put("workerGroups", workerGroups); + Map headers = new HashMap<>(); + headers.put(Constants.SESSION_ID_KEY, sessionId); + + RequestClient requestClient = new RequestClient(); + String url = String.format("/projects/%d/worker-group", projectCode); + return requestClient.post(url, headers, params); + } + } diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json index 71385fa9f386..1a160d747422 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/workflow-json/test.json @@ -19,7 +19,7 @@ "timeout": 0, "timeoutFlag": "CLOSE", "timeoutNotifyStrategy": "", - "workerGroup": "", + "workerGroup": "default", "cpuQuota": -1, "memoryMax": -1, "taskExecuteType": "BATCH" From 2bde4cef7f7c8a74144602303670f35a0d87043d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 16 Jun 2026 19:52:18 +0800 Subject: [PATCH 06/27] The default worker group is assigned to all projects by default. --- .../service/impl/ProjectWorkerGroupRelationServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java index d4d0e8575f4f..28af44a491d1 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java @@ -31,6 +31,7 @@ import org.apache.dolphinscheduler.dao.repository.ScheduleDao; import org.apache.dolphinscheduler.dao.repository.TaskDefinitionDao; import org.apache.dolphinscheduler.dao.repository.WorkerGroupDao; +import org.apache.dolphinscheduler.dao.utils.WorkerGroupUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.SetUtils; @@ -244,7 +245,7 @@ private Set getAllUsedWorkerGroups(Project project) { @Override public boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGroup) { - if (StringUtils.isEmpty(workerGroup)) { + if (WorkerGroupUtils.isWorkerGroupEmpty(workerGroup)) { return true; } return getAllAssignedWorkerGroupNames(projectCode).contains(workerGroup); From 1ad45ac7e6ac5f7322e8d3226d7e6a88626729be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Wed, 17 Jun 2026 17:04:36 +0800 Subject: [PATCH 07/27] Fix E2E test failure: wait for task modal to close before workflow save --- .../e2e/pages/project/workflow/task/TaskNodeForm.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java index bec099385d00..62b8d034ba49 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java @@ -185,6 +185,9 @@ public TaskNodeForm selectResource(String resourceName) { public WorkflowForm submit() { buttonSubmit.click(); + WebDriverWaitFactory.createWebDriverWait(parent().driver()) + .until(ExpectedConditions.invisibilityOfElementLocated(By.className("n-modal-mask"))); + return parent(); } } From 74919b07266f5845567ae61f0ab897df474d15e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 22 Jun 2026 11:03:43 +0800 Subject: [PATCH 08/27] Fix E2E test failure: Wait for save button to be clickable in WorkflowForm --- .../e2e/pages/project/workflow/WorkflowForm.java | 2 ++ .../e2e/pages/project/workflow/task/TaskNodeForm.java | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java index 7c47eb36a1dd..cb412c2e8494 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java @@ -111,6 +111,8 @@ public WebElement getTask(String taskName) { } public WorkflowSaveDialog submit() { + WebDriverWaitFactory.createWebDriverWait(driver) + .until(ExpectedConditions.elementToBeClickable(buttonSave())); buttonSave().click(); WebDriverWaitFactory.createWebDriverWait(driver) .until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(.,'Basic Information')]"))); diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java index 62b8d034ba49..bec099385d00 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java @@ -185,9 +185,6 @@ public TaskNodeForm selectResource(String resourceName) { public WorkflowForm submit() { buttonSubmit.click(); - WebDriverWaitFactory.createWebDriverWait(parent().driver()) - .until(ExpectedConditions.invisibilityOfElementLocated(By.className("n-modal-mask"))); - return parent(); } } From b4c698b382c890f046b4415e54a776c24c5bab79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 22 Jun 2026 14:16:58 +0800 Subject: [PATCH 09/27] Resolve workerGroup validation race condition in E2E tests --- .../e2e/pages/project/workflow/WorkflowForm.java | 6 ++++-- .../task/components/node/fields/use-worker-group.ts | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java index cb412c2e8494..368fad2d148c 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java @@ -111,8 +111,10 @@ public WebElement getTask(String taskName) { } public WorkflowSaveDialog submit() { - WebDriverWaitFactory.createWebDriverWait(driver) - .until(ExpectedConditions.elementToBeClickable(buttonSave())); + WebDriverWaitFactory.createWebDriverWait(driver, 5) + .ignoring(org.openqa.selenium.TimeoutException.class) + .until(ExpectedConditions.invisibilityOfElementLocated(By.className("n-modal-mask"))); + buttonSave().click(); WebDriverWaitFactory.createWebDriverWait(driver) .until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(.,'Basic Information')]"))); diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts index 401684411354..a04e9900b8c6 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts @@ -52,8 +52,11 @@ export function useWorkerGroup(projectCode: number): IJsonItem { options: options, validate: { trigger: ['input', 'blur'], - required: true, - message: t('project.node.worker_group_tips') + validator: (rule: any, value: string) => { + if (options.value.length === 0) return Promise.resolve() + if (!value) return Promise.reject(new Error(t('project.node.worker_group_tips'))) + return Promise.resolve() + } }, value: options.value.length > 0 ? options.value[0].value : '' } From f2531a5738c403041f62de6d78d8d565773b12a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 22 Jun 2026 14:27:58 +0800 Subject: [PATCH 10/27] format frontend code --- .../projects/task/components/node/fields/use-worker-group.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts index a04e9900b8c6..744891218737 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts @@ -54,7 +54,8 @@ export function useWorkerGroup(projectCode: number): IJsonItem { trigger: ['input', 'blur'], validator: (rule: any, value: string) => { if (options.value.length === 0) return Promise.resolve() - if (!value) return Promise.reject(new Error(t('project.node.worker_group_tips'))) + if (!value) + return Promise.reject(new Error(t('project.node.worker_group_tips'))) return Promise.resolve() } }, From 1ab127d1bf22414331cb4620fa32c657f7b69b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 23 Jun 2026 16:16:59 +0800 Subject: [PATCH 11/27] remove WebDriverWaitFactory.createWebDriverWait --- .../e2e/pages/project/workflow/WorkflowForm.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java index 368fad2d148c..7c47eb36a1dd 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java @@ -111,10 +111,6 @@ public WebElement getTask(String taskName) { } public WorkflowSaveDialog submit() { - WebDriverWaitFactory.createWebDriverWait(driver, 5) - .ignoring(org.openqa.selenium.TimeoutException.class) - .until(ExpectedConditions.invisibilityOfElementLocated(By.className("n-modal-mask"))); - buttonSave().click(); WebDriverWaitFactory.createWebDriverWait(driver) .until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(.,'Basic Information')]"))); From 9b1fa14fc284a40997aed49f0df813eec1e7da14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Thu, 25 Jun 2026 13:51:45 +0800 Subject: [PATCH 12/27] add WebDriverWaitFactory.createWebDriverWait --- .../e2e/pages/project/workflow/WorkflowForm.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java index 7c47eb36a1dd..7811648222dd 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/WorkflowForm.java @@ -26,6 +26,7 @@ import org.apache.dolphinscheduler.e2e.pages.project.workflow.task.SwitchTaskForm; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.List; import lombok.Getter; @@ -111,6 +112,8 @@ public WebElement getTask(String taskName) { } public WorkflowSaveDialog submit() { + WebDriverWaitFactory.createWebDriverWait(driver, Duration.ofSeconds(5)) + .until(ExpectedConditions.invisibilityOfElementLocated(By.className("n-modal-mask"))); buttonSave().click(); WebDriverWaitFactory.createWebDriverWait(driver) .until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(.,'Basic Information')]"))); From ecc17f3c010a66df48794937e59d84d2c0dabace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Thu, 25 Jun 2026 15:15:18 +0800 Subject: [PATCH 13/27] Fix E2E tests: add setWorkerGroup() for all task types to prevent modal mask interception --- .../e2e/cases/WorkflowE2ETest.java | 1 + .../e2e/cases/WorkflowHttpTaskE2ETest.java | 1 + .../e2e/cases/WorkflowJavaTaskE2ETest.java | 2 ++ .../e2e/cases/WorkflowSwitchE2ETest.java | 3 +++ .../e2e/cases/tasks/PythonTaskE2ETest.java | 6 +++++ .../e2e/cases/tasks/ShellTaskE2ETest.java | 6 +++++ .../project/workflow/task/TaskNodeForm.java | 26 +++++++++++++++++++ .../node/fields/use-worker-group.ts | 9 +++---- 8 files changed, 48 insertions(+), 6 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java index 7ad0ff08e6e7..da36b6b49cfd 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java @@ -118,6 +118,7 @@ void testCreateWorkflow() { .script("echo ${today}\necho ${global_param}\n") .name("test-1") .addParam("today", "${system.datetime}") + .setWorkerGroup("default") .submit() .submit() diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java index 0408b384b94c..335e8fe7a2fd 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java @@ -117,6 +117,7 @@ void testCreateWorkflow() { .url(mockServerUrl) .name("test-1") .addParam("today", "${system.datetime}") + .setWorkerGroup("default") .submit() .submit() diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java index 8d7a662ad2a5..97d44187ff8a 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java @@ -264,6 +264,7 @@ void testCreateFatJarWorkflow() { .selectJavaResource("fat.jar") .name("test-1") .selectEnv(environmentName) + .setWorkerGroup("default") .submit() .submit() .name(workflow) @@ -343,6 +344,7 @@ void testCreateNormalJarWorkflow() { .selectJavaResource("normal2.jar") .name("test-2") .selectEnv(environmentName) + .setWorkerGroup("default") .submit() .submit() .name(workflow2) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java index f95338836bd6..20f306f9a48d 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java @@ -99,6 +99,7 @@ void testCreateSwitchWorkflow() { workflowForm.addTask(TaskType.SHELL) .script("echo ${today}\necho ${global_param}\n") .name("pre-task") + .setWorkerGroup("default") .submit(); SwitchTaskForm switchTaskForm = workflowForm.addTask(TaskType.SWITCH); @@ -110,12 +111,14 @@ void testCreateSwitchWorkflow() { .script("echo ${key}") .preTask("switch") .name(ifBranchName) + .setWorkerGroup("default") .submit(); workflowForm.addTask(TaskType.SHELL) .script("echo ${key}") .preTask("switch") .name(elseBranchName) + .setWorkerGroup("default") .submit(); // format dag diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java index cb18e5884879..b89e371a1299 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java @@ -97,6 +97,7 @@ void testRunPythonTasks_SuccessCase() { .addTask(WorkflowForm.TaskType.PYTHON) .script(pythonScripts) .name(taskName) + .setWorkerGroup("default") .submit() .submit() @@ -139,6 +140,7 @@ void testRunPythonTasks_WorkflowParamsCase() { .addTask(WorkflowForm.TaskType.PYTHON) .script(pythonScripts) .name(taskName) + .setWorkerGroup("default") .submit() .submit() @@ -182,6 +184,7 @@ void testRunPythonTasks_LocalParamsCase() { .script(pythonScripts) .name(taskName) .addParam("name", "tom") + .setWorkerGroup("default") .submit() .submit() @@ -224,6 +227,7 @@ void testRunPythonTasks_GlobalParamsOverrideLocalParamsCase() { .script(pythonScripts) .name(taskName) .addParam("name", "tom") + .setWorkerGroup("default") .submit() .submit() @@ -281,6 +285,7 @@ void testRunPythonTasks_UsingResourceFile() { .name(taskName) .selectResource(testFileName) .addParam("file_name", String.format("%s.sh", testFileName)) + .setWorkerGroup("default") .submit() .submit() @@ -318,6 +323,7 @@ void testRunPythonTasks_FailedCase() { .addTask(WorkflowForm.TaskType.PYTHON) .script(pythonScripts) .name(taskName) + .setWorkerGroup("default") .submit() .submit() diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java index 7a547201c97d..181bc8d1df36 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java @@ -84,6 +84,7 @@ void testRunShellTasks_SuccessCase() { .addTask(WorkflowForm.TaskType.SHELL) .script("echo hello world\n") .name(taskName) + .setWorkerGroup("default") .submit() .submit() @@ -119,6 +120,7 @@ void testRunShellTasks_WorkflowParamsCase() { .addTask(WorkflowForm.TaskType.SHELL) .script("[ \"${name}\" = \"tom\" ] && echo \"success\" || { echo \"failed\"; exit 1; }") .name(taskName) + .setWorkerGroup("default") .submit() .submit() @@ -155,6 +157,7 @@ void testRunShellTasks_LocalParamsCase() { .script("[ \"${name}\" = \"tom\" ] && echo \"success\" || { echo \"failed\"; exit 1; }") .name(taskName) .addParam("name", "tom") + .setWorkerGroup("default") .submit() .submit() @@ -190,6 +193,7 @@ void testRunShellTasks_GlobalParamsOverrideLocalParamsCase() { .script("[ \"${name}\" = \"jerry\" ] && echo \"success\" || { echo \"failed\"; exit 1; }") .name(taskName) .addParam("name", "tom") + .setWorkerGroup("default") .submit() .submit() @@ -232,6 +236,7 @@ void testRunShellTasks_UsingResourceFile() { .script("cat " + testFileName + ".sh") .name(taskName) .selectResource(testFileName) + .setWorkerGroup("default") .submit() .submit() @@ -266,6 +271,7 @@ void testRunShellTasks_FailedCase() { .addTask(WorkflowForm.TaskType.SHELL) .script("echo 'I am failed'\n exit1\n") .name(taskName) + .setWorkerGroup("default") .submit() .submit() diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java index bec099385d00..8767d61b8209 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java @@ -70,6 +70,12 @@ public abstract class TaskNodeForm { }) private WebElement selectEnv; + @FindBys({ + @FindBy(className = "worker-group-select"), + @FindBy(className = "n-base-selection"), + }) + private WebElement selectWorkerGroup; + @FindBys({ @FindBy(className = "btn-custom-parameters"), @FindBy(tagName = "button"), @@ -142,6 +148,26 @@ public TaskNodeForm selectEnv(String envName) { return this; } + public TaskNodeForm setWorkerGroup(String workerGroupName) { + ((JavascriptExecutor) parent().driver()).executeScript( + "document.querySelector('.worker-group-select .n-base-selection').click()"); + + try { + WebDriverWaitFactory.createWebDriverWait(parent().driver(), Duration.ofSeconds(2)) + .until(ExpectedConditions.elementToBeClickable( + By.xpath("//*[contains(@class, 'n-base-selection-option') and text()='" + workerGroupName + + "']"))) + .click(); + } catch (org.openqa.selenium.TimeoutException e) { + ((JavascriptExecutor) parent().driver()).executeScript( + "arguments[0].value = '" + workerGroupName + "'", + selectWorkerGroup); + parent.driver().switchTo().activeElement().sendKeys(Keys.ESCAPE); + } + + return this; + } + public TaskNodeForm preTask(String preTaskName) { ((JavascriptExecutor) parent().driver()).executeScript("arguments[0].click();", selectPreTasks); diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts index 744891218737..597adb218d85 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-worker-group.ts @@ -44,6 +44,7 @@ export function useWorkerGroup(projectCode: number): IJsonItem { return { type: 'select', field: 'workerGroup', + class: 'worker-group-select', span: 12, name: t('project.node.worker_group'), props: { @@ -52,12 +53,8 @@ export function useWorkerGroup(projectCode: number): IJsonItem { options: options, validate: { trigger: ['input', 'blur'], - validator: (rule: any, value: string) => { - if (options.value.length === 0) return Promise.resolve() - if (!value) - return Promise.reject(new Error(t('project.node.worker_group_tips'))) - return Promise.resolve() - } + required: true, + message: t('project.node.worker_group_tips') }, value: options.value.length > 0 ? options.value[0].value : '' } From 62bd6ce6d5d7234052c988263b2dcdb987202803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 29 Jun 2026 10:14:27 +0800 Subject: [PATCH 14/27] add import --- .../e2e/pages/project/workflow/task/TaskNodeForm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java index 8767d61b8209..13841bea3d2b 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java @@ -20,6 +20,7 @@ import org.apache.dolphinscheduler.e2e.core.WebDriverWaitFactory; import org.apache.dolphinscheduler.e2e.pages.project.workflow.WorkflowForm; +import java.time.Duration; import java.util.List; import lombok.Getter; From fb82c9bf5696b287f7c446a5fc069039d27f846f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 29 Jun 2026 10:47:18 +0800 Subject: [PATCH 15/27] Pre-assign project worker group before selecting task worker group in E2E tests. --- .../e2e/cases/WorkflowE2ETest.java | 2 +- .../e2e/cases/WorkflowHttpTaskE2ETest.java | 2 +- .../e2e/cases/WorkflowJavaTaskE2ETest.java | 2 +- .../e2e/cases/WorkflowSwitchE2ETest.java | 2 +- .../e2e/pages/project/ProjectPage.java | 2 +- .../project/workflow/task/TaskNodeForm.java | 30 +++++++++---------- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java index da36b6b49cfd..4895a501cd77 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java @@ -80,7 +80,7 @@ public static void setup() { userPage.update(user, user, email, phone, tenant) .goToNav(ProjectPage.class) - .create(project); + .createProjectUntilSuccess(project); } @AfterAll diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java index 335e8fe7a2fd..3665a16be8a5 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java @@ -80,7 +80,7 @@ public static void setup() { userPage.update(user, user, email, phone, tenant) .goToNav(ProjectPage.class) - .create(project); + .createProjectUntilSuccess(project); } @AfterAll diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java index 97d44187ff8a..26518f600b6e 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java @@ -208,7 +208,7 @@ public static void setup() { userPage.update(user, user, email, phone, tenant) .goToNav(ProjectPage.class) - .create(project); + .createProjectUntilSuccess(project); ProjectPage projectPage = new ProjectPage(browser); Awaitility.await().untilAsserted(() -> assertThat(projectPage.projectList()) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java index 20f306f9a48d..6e7232e83da3 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java @@ -66,7 +66,7 @@ public static void setup() { .goToTab(TenantPage.class) .create(tenant) .goToNav(ProjectPage.class) - .create(project); + .createProjectUntilSuccess(project); } @AfterAll diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java index 69dbb176d9c6..d6573eb3b80c 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java @@ -71,10 +71,10 @@ public ProjectPage create(String project) { public ProjectPage createProjectUntilSuccess(String project) { create(project); - assignWorkerGroup(project, "default"); await().untilAsserted(() -> assertThat(projectList()) .as("project list should contain newly-created project") .anyMatch(it -> it.getText().contains(project))); + assignWorkerGroup(project, "default"); return this; } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java index 13841bea3d2b..0a4884fcc76f 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/workflow/task/TaskNodeForm.java @@ -150,21 +150,21 @@ public TaskNodeForm selectEnv(String envName) { } public TaskNodeForm setWorkerGroup(String workerGroupName) { - ((JavascriptExecutor) parent().driver()).executeScript( - "document.querySelector('.worker-group-select .n-base-selection').click()"); - - try { - WebDriverWaitFactory.createWebDriverWait(parent().driver(), Duration.ofSeconds(2)) - .until(ExpectedConditions.elementToBeClickable( - By.xpath("//*[contains(@class, 'n-base-selection-option') and text()='" + workerGroupName - + "']"))) - .click(); - } catch (org.openqa.selenium.TimeoutException e) { - ((JavascriptExecutor) parent().driver()).executeScript( - "arguments[0].value = '" + workerGroupName + "'", - selectWorkerGroup); - parent.driver().switchTo().activeElement().sendKeys(Keys.ESCAPE); - } + ((JavascriptExecutor) parent().driver()).executeScript("arguments[0].click();", selectWorkerGroup); + + final By optionsLocator = By.xpath( + "//div[contains(@class, 'n-select-menu')]//div[contains(@class, 'n-base-select-option')]"); + + WebDriverWaitFactory.createWebDriverWait(parent().driver(), Duration.ofSeconds(5)) + .until(ExpectedConditions.visibilityOfElementLocated(optionsLocator)); + + parent().driver() + .findElements(optionsLocator) + .stream() + .filter(it -> workerGroupName.equals(it.getText().trim())) + .findFirst() + .orElseThrow(() -> new RuntimeException("No such worker group: " + workerGroupName)) + .click(); return this; } From 469315a6165e5e8d4a2bdd18522582b8c3503d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 20 Jul 2026 14:46:26 +0800 Subject: [PATCH 16/27] Reject empty/default as implicit worker group; require explicit assignment; add test cases --- ...ProjectWorkerGroupRelationServiceImpl.java | 5 +- .../api/validator/WorkerGroupValidator.java | 22 +- ...ProjectWorkerGroupRelationServiceTest.java | 97 +++++++++ .../validator/WorkerGroupValidatorTest.java | 190 +++++++++++++++--- 4 files changed, 271 insertions(+), 43 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java index 28af44a491d1..d66295e59234 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectWorkerGroupRelationServiceImpl.java @@ -31,7 +31,6 @@ import org.apache.dolphinscheduler.dao.repository.ScheduleDao; import org.apache.dolphinscheduler.dao.repository.TaskDefinitionDao; import org.apache.dolphinscheduler.dao.repository.WorkerGroupDao; -import org.apache.dolphinscheduler.dao.utils.WorkerGroupUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.SetUtils; @@ -245,8 +244,8 @@ private Set getAllUsedWorkerGroups(Project project) { @Override public boolean isWorkerGroupAssignedToProject(Long projectCode, String workerGroup) { - if (WorkerGroupUtils.isWorkerGroupEmpty(workerGroup)) { - return true; + if (StringUtils.isEmpty(workerGroup)) { + return false; } return getAllAssignedWorkerGroupNames(projectCode).contains(workerGroup); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java index 10667764b790..452b32ea2bda 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidator.java @@ -49,8 +49,15 @@ public void validate(final WorkerGroupValidationContext context) { String workerGroup = context.getWorkerGroup(); long projectCode = context.getProjectCode(); - if (StringUtils.isNotBlank(workerGroup) - && !projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, workerGroup)) { + if (StringUtils.isEmpty(workerGroup)) { + log.warn("Worker group is empty or null for project {}", projectCode); + throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); + } + + Set assignedWorkerGroupNames = + projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(projectCode); + + if (assignedWorkerGroupNames == null || !assignedWorkerGroupNames.contains(workerGroup)) { log.warn("Worker group {} is not assigned to project {}", workerGroup, projectCode); throw new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT, workerGroup); } @@ -68,15 +75,10 @@ public void validate(final List workerGroups, final long projectCode) { return; } - List validWorkerGroups = workerGroups.stream() - .filter(StringUtils::isNotBlank) + List distinctWorkerGroups = workerGroups.stream() .distinct() .collect(Collectors.toList()); - if (CollectionUtils.isEmpty(validWorkerGroups)) { - return; - } - Set assignedWorkerGroupNames = projectWorkerGroupRelationService .getAllAssignedWorkerGroupNames(projectCode); @@ -85,8 +87,8 @@ public void validate(final List workerGroups, final long projectCode) { } Set finalAssignedWorkerGroupNames = assignedWorkerGroupNames; - List unassignedWorkerGroups = validWorkerGroups.stream() - .filter(wg -> !finalAssignedWorkerGroupNames.contains(wg)) + List unassignedWorkerGroups = distinctWorkerGroups.stream() + .filter(wg -> StringUtils.isEmpty(wg) || !finalAssignedWorkerGroupNames.contains(wg)) .collect(Collectors.toList()); if (!unassignedWorkerGroups.isEmpty()) { diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationServiceTest.java index 0bb91d22a328..7c8fbaf6d5ec 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectWorkerGroupRelationServiceTest.java @@ -259,4 +259,101 @@ private TaskDefinition getTaskDefinitionWithDiffWorkerGroup() { taskDefinition.setWorkerGroup("new"); return taskDefinition; } + + @Test + public void testIsWorkerGroupAssignedToProject() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(getProject()); + Mockito.when(projectWorkerGroupDao.queryAssignedWorkerGroupNamesByProjectCode(projectCode)) + .thenReturn(Sets.newHashSet("g_suyc")); + Mockito.when(taskDefinitionDao.queryAllTaskDefinitionWorkerGroups(projectCode)) + .thenReturn(new ArrayList<>()); + Mockito.when(scheduleDao.querySchedulerListByProjectName(Mockito.any())) + .thenReturn(Lists.newArrayList()); + + Assertions.assertTrue(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "g_suyc")); + + Assertions.assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "")); + + Assertions + .assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "default")); + + Assertions.assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, null)); + + Assertions.assertFalse( + projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "unassigned_group")); + } + + @Test + public void testIsWorkerGroupAssignedToProjectWithDefaultAssigned() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(getProject()); + Mockito.when(projectWorkerGroupDao.queryAssignedWorkerGroupNamesByProjectCode(projectCode)) + .thenReturn(Sets.newHashSet("g_suyc", "default")); + Mockito.when(taskDefinitionDao.queryAllTaskDefinitionWorkerGroups(projectCode)) + .thenReturn(new ArrayList<>()); + Mockito.when(scheduleDao.querySchedulerListByProjectName(Mockito.any())) + .thenReturn(Lists.newArrayList()); + + Assertions.assertTrue(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "g_suyc")); + + Assertions.assertTrue(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "default")); + + Assertions.assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "")); + + Assertions.assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, null)); + + Assertions.assertFalse( + projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "unassigned_group")); + } + + @Test + public void testIsWorkerGroupAssignedToProjectWithUsedWorkerGroups() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(getProject()); + Mockito.when(projectWorkerGroupDao.queryAssignedWorkerGroupNamesByProjectCode(projectCode)) + .thenReturn(Sets.newHashSet("g_suyc")); + Mockito.when(taskDefinitionDao.queryAllTaskDefinitionWorkerGroups(projectCode)) + .thenReturn(Lists.newArrayList("group-from-task")); + + org.apache.dolphinscheduler.dao.entity.Schedule schedule = + new org.apache.dolphinscheduler.dao.entity.Schedule(); + schedule.setWorkerGroup("group-from-schedule"); + Mockito.when(scheduleDao.querySchedulerListByProjectName(Mockito.any())) + .thenReturn(Lists.newArrayList(schedule)); + + Assertions.assertTrue(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "g_suyc")); + + Assertions.assertTrue( + projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "group-from-task")); + + Assertions.assertTrue( + projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "group-from-schedule")); + + Assertions + .assertFalse(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "default")); + + Assertions.assertFalse( + projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(projectCode, "unassigned_group")); + } + + @Test + public void testGetAllAssignedWorkerGroupNamesWithUsedWorkerGroups() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(getProject()); + Mockito.when(projectWorkerGroupDao.queryAssignedWorkerGroupNamesByProjectCode(projectCode)) + .thenReturn(Sets.newHashSet("g_suyc")); + Mockito.when(taskDefinitionDao.queryAllTaskDefinitionWorkerGroups(projectCode)) + .thenReturn(Lists.newArrayList("group-from-task")); + + org.apache.dolphinscheduler.dao.entity.Schedule schedule = + new org.apache.dolphinscheduler.dao.entity.Schedule(); + schedule.setWorkerGroup("group-from-schedule"); + Mockito.when(scheduleDao.querySchedulerListByProjectName(Mockito.any())) + .thenReturn(Lists.newArrayList(schedule)); + + java.util.Set assignedGroups = + projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(projectCode); + + Assertions.assertEquals(3, assignedGroups.size()); + Assertions.assertTrue(assignedGroups.contains("g_suyc")); + Assertions.assertTrue(assignedGroups.contains("group-from-task")); + Assertions.assertTrue(assignedGroups.contains("group-from-schedule")); + } } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java index b506cf00f3dd..c1e0c9571e0a 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/WorkerGroupValidatorTest.java @@ -54,8 +54,8 @@ void testValidate_nullWorkerGroup() { .projectCode(PROJECT_CODE) .build(); - assertThatCode(() -> workerGroupValidator.validate(context)) - .doesNotThrowAnyException(); + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); } @Test @@ -65,8 +65,8 @@ void testValidate_emptyWorkerGroup() { .projectCode(PROJECT_CODE) .build(); - assertThatCode(() -> workerGroupValidator.validate(context)) - .doesNotThrowAnyException(); + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); } @Test @@ -76,15 +76,16 @@ void testValidate_blankWorkerGroup() { .projectCode(PROJECT_CODE) .build(); - assertThatCode(() -> workerGroupValidator.validate(context)) - .doesNotThrowAnyException(); + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); } @Test void testValidate_validWorkerGroup() { - String validWorkerGroup = "default"; - when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, validWorkerGroup)) - .thenReturn(true); + String validWorkerGroup = "g_suyc"; + Set assignedGroups = new HashSet<>(Collections.singletonList(validWorkerGroup)); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() .workerGroup(validWorkerGroup) @@ -98,8 +99,9 @@ void testValidate_validWorkerGroup() { @Test void testValidate_invalidWorkerGroup() { String invalidWorkerGroup = "invalid-group"; - when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, invalidWorkerGroup)) - .thenReturn(false); + Set assignedGroups = new HashSet<>(Collections.singletonList("g_suyc")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() .workerGroup(invalidWorkerGroup) @@ -111,15 +113,52 @@ void testValidate_invalidWorkerGroup() { .hasMessageContaining(invalidWorkerGroup); } + @Test + void testValidate_defaultNotAssigned() { + String workerGroup = "default"; + Set assignedGroups = new HashSet<>(Collections.singletonList("g_suyc")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + + // "default" should fail when not explicitly assigned + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("default"); + } + + @Test + void testValidate_defaultAssigned() { + String workerGroup = "default"; + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "default")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + + // "default" should pass when explicitly assigned + assertThatCode(() -> workerGroupValidator.validate(context)) + .doesNotThrowAnyException(); + } + @Test void testValidate_differentProjectCode() { long anotherProjectCode = 2L; - String workerGroup = "default"; + String workerGroup = "g_suyc"; - when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, workerGroup)) - .thenReturn(true); - when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(anotherProjectCode, workerGroup)) - .thenReturn(false); + Set assignedGroups1 = new HashSet<>(Collections.singletonList(workerGroup)); + Set assignedGroups2 = new HashSet<>(Collections.singletonList("other-group")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups1); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(anotherProjectCode)) + .thenReturn(assignedGroups2); // Should pass for PROJECT_CODE WorkerGroupValidationContext validContext = WorkerGroupValidationContext.builder() @@ -140,9 +179,41 @@ void testValidate_differentProjectCode() { @Test void testValidate_caseSensitive() { - String workerGroup = "Default"; - when(projectWorkerGroupRelationService.isWorkerGroupAssignedToProject(PROJECT_CODE, workerGroup)) - .thenReturn(false); + String workerGroup = "G_SUYC"; + Set assignedGroups = new HashSet<>(Collections.singletonList("g_suyc")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testValidate_assignedWorkerGroupsNull() { + String workerGroup = "g_suyc"; + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(null); + + WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(PROJECT_CODE) + .build(); + + assertThatThrownBy(() -> workerGroupValidator.validate(context)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testValidate_assignedWorkerGroupsEmpty() { + String workerGroup = "g_suyc"; + Set assignedGroups = new HashSet<>(); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); WorkerGroupValidationContext context = WorkerGroupValidationContext.builder() .workerGroup(workerGroup) @@ -168,14 +239,15 @@ void testBatchValidate_emptyList() { @Test void testBatchValidate_listWithOnlyEmptyStrings() { List workerGroups = Arrays.asList("", " ", null); - assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) - .doesNotThrowAnyException(); + // Empty/null values should cause validation failure + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class); } @Test void testBatchValidate_allValidWorkerGroups() { - List workerGroups = Arrays.asList("default", "group-a", "group-b"); - Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a", "group-b")); + List workerGroups = Arrays.asList("g_suyc", "group-a", "group-b"); + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "group-a", "group-b")); when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) .thenReturn(assignedGroups); @@ -185,8 +257,8 @@ void testBatchValidate_allValidWorkerGroups() { @Test void testBatchValidate_someInvalidWorkerGroups() { - List workerGroups = Arrays.asList("default", "invalid-group"); - Set assignedGroups = new HashSet<>(Collections.singletonList("default")); + List workerGroups = Arrays.asList("g_suyc", "invalid-group"); + Set assignedGroups = new HashSet<>(Collections.singletonList("g_suyc")); when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) .thenReturn(assignedGroups); @@ -210,8 +282,8 @@ void testBatchValidate_allInvalidWorkerGroups() { @Test void testBatchValidate_withDuplicates() { - List workerGroups = Arrays.asList("default", "default", "group-a"); - Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a")); + List workerGroups = Arrays.asList("g_suyc", "g_suyc", "group-a"); + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "group-a")); when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) .thenReturn(assignedGroups); @@ -222,13 +294,71 @@ void testBatchValidate_withDuplicates() { @Test void testBatchValidate_withEmptyStrings() { - List workerGroups = Arrays.asList("default", "", "group-a"); - Set assignedGroups = new HashSet<>(Arrays.asList("default", "group-a")); + List workerGroups = Arrays.asList("g_suyc", "", "group-a"); + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "group-a")); when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) .thenReturn(assignedGroups); - // Should not throw because empty strings are filtered out + // Should throw because empty strings are now invalid + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testBatchValidate_defaultNotAssigned() { + List workerGroups = Arrays.asList("g_suyc", "default"); + Set assignedGroups = new HashSet<>(Collections.singletonList("g_suyc")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + // "default" should fail when not explicitly assigned + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class) + .hasMessageContaining("default"); + } + + @Test + void testBatchValidate_defaultAssigned() { + List workerGroups = Arrays.asList("g_suyc", "default"); + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "default")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + // "default" should pass when explicitly assigned assertThatCode(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) .doesNotThrowAnyException(); } + + @Test + void testBatchValidate_assignedWorkerGroupsNull() { + List workerGroups = Arrays.asList("g_suyc", "group-a"); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(null); + + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testBatchValidate_assignedWorkerGroupsEmpty() { + List workerGroups = Arrays.asList("g_suyc", "group-a"); + Set assignedGroups = new HashSet<>(); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class); + } + + @Test + void testBatchValidate_mixedNullAndValidValues() { + List workerGroups = Arrays.asList("g_suyc", null, "group-a"); + Set assignedGroups = new HashSet<>(Arrays.asList("g_suyc", "group-a")); + when(projectWorkerGroupRelationService.getAllAssignedWorkerGroupNames(PROJECT_CODE)) + .thenReturn(assignedGroups); + + // Should fail because null is invalid even though other groups are valid + assertThatThrownBy(() -> workerGroupValidator.validate(workerGroups, PROJECT_CODE)) + .isInstanceOf(ServiceException.class); + } } From fde389d082410c3d285b1b10997a94b8ef0213e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 20 Jul 2026 15:19:20 +0800 Subject: [PATCH 17/27] Assign default worker group to projects before creating workflows in API tests --- .../dolphinscheduler/api/test/cases/ExecutorAPITest.java | 2 ++ .../api/test/cases/WorkflowDefinitionAPITest.java | 3 +++ .../api/test/cases/WorkflowInstanceAPITest.java | 2 ++ .../api/test/cases/tasks/DependentTaskAPITest.java | 2 ++ .../api/test/cases/tasks/EmrServerlessTaskAPITest.java | 2 ++ .../api/test/cases/tasks/GrpcTaskAPITest.java | 4 ++++ 6 files changed, 15 insertions(+) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java index 667016f5b8a8..ce5d85c94eed 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java @@ -105,6 +105,8 @@ public void testStartWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java index b58e268e6ca0..0feccf25e38d 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java @@ -94,6 +94,9 @@ public void testCreateWorkflowDefinition() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); HttpResponse createWorkflowDefinitionResponse = workflowDefinitionPage diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java index a13019df8f19..1275afce6f6a 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java @@ -116,6 +116,8 @@ public void testQueryWorkflowInstancesByWorkflowInstanceId() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java index 9d15e4c92bc4..31e307065292 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java @@ -122,6 +122,8 @@ public void testCreateUpstreamWorkflow() throws Exception { Assertions.assertNotEquals(0, projectCode, "project should be found by name"); log.info("project code: {}", projectCode); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // create upstream workflow definition (shell task: echo hello) ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java index 655f4026d856..a5169a4a052c 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java @@ -106,6 +106,8 @@ public void testEmrServerlessSuccessWorkflowInstance() throws Exception { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java index df7ceb2fa178..7175670d6ef8 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java @@ -103,6 +103,8 @@ public void testGrpcFailedWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/task-grpc/grpcFailedWorkflow.json").getFile()); @@ -153,6 +155,8 @@ public void testGrpcSuccessWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/task-grpc/grpcSuccessWorkflow.json").getFile()); From 1c0dfe05fa54c8c7c39e7f1623e2f760b3cd0990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 21 Jul 2026 16:28:00 +0800 Subject: [PATCH 18/27] Fix array parameter serialization in RequestClient.getParams() --- .../api/test/utils/RequestClient.java | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java index 37faa358ea9f..eabde7de30dc 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java @@ -31,10 +31,13 @@ import org.apache.http.message.BasicHeader; import java.io.File; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashMap; import java.util.Map; -import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -95,20 +98,55 @@ public HttpResponse get(String url, Map headers, Map params) { - StringBuilder sb = new StringBuilder(Constants.QUESTION_MARK); - if (!params.isEmpty()) { - for (Map.Entry item : params.entrySet()) { - Object value = item.getValue(); - if (Objects.nonNull(value)) { - sb.append(Constants.AND_MARK); - sb.append(item.getKey()); - sb.append(Constants.EQUAL_MARK); - sb.append(value); + if (params == null || params.isEmpty()) { + return ""; + } + + if (!(params instanceof SortedMap)) { + params = new TreeMap<>(params); + } + + StringBuilder sb = new StringBuilder(params.size() * 16); + boolean isFirst = true; + + for (Map.Entry entry : params.entrySet()) { + Object value = entry.getValue(); + if (value == null) { + continue; + } + + String key = entry.getKey(); + + if (value.getClass().isArray()) { + int length = java.lang.reflect.Array.getLength(value); + for (int i = 0; i < length; i++) { + Object item = java.lang.reflect.Array.get(value, i); + if (item != null) { + appendParam(sb, isFirst, key, item.toString()); + isFirst = false; + } } + } else { + appendParam(sb, isFirst, key, value.toString()); + isFirst = false; } - return sb.toString(); + } + + return sb.toString(); + } + + private static void appendParam(StringBuilder sb, boolean isFirst, String key, String value) { + if (isFirst) { + sb.append(Constants.QUESTION_MARK); } else { - return ""; + sb.append(Constants.AND_MARK); + } + try { + sb.append(URLEncoder.encode(key, StandardCharsets.UTF_8.name())) + .append(Constants.EQUAL_MARK) + .append(URLEncoder.encode(value, StandardCharsets.UTF_8.name())); + } catch (Exception e) { + sb.append(key).append(Constants.EQUAL_MARK).append(value); } } From 6df6870d71f5b68f8b916026ec86b2b4a77fda36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Tue, 21 Jul 2026 16:48:15 +0800 Subject: [PATCH 19/27] Add assertions for assignWorkerGroups in API tests --- .../dolphinscheduler/api/test/cases/ExecutorAPITest.java | 4 +++- .../api/test/cases/WorkflowDefinitionAPITest.java | 4 +++- .../api/test/cases/WorkflowInstanceAPITest.java | 4 +++- .../api/test/cases/tasks/DependentTaskAPITest.java | 4 +++- .../api/test/cases/tasks/EmrServerlessTaskAPITest.java | 4 +++- .../api/test/cases/tasks/GrpcTaskAPITest.java | 8 ++++++-- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java index ce5d85c94eed..c79cba095000 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java @@ -105,7 +105,9 @@ public void testStartWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java index 0feccf25e38d..a05782c946d1 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowDefinitionAPITest.java @@ -95,7 +95,9 @@ public void testCreateWorkflowDefinition() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("workflow-json/test.json").getFile()); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java index 1275afce6f6a..45ec0e6314aa 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/WorkflowInstanceAPITest.java @@ -116,7 +116,9 @@ public void testQueryWorkflowInstancesByWorkflowInstanceId() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java index 31e307065292..bd4f45e6754d 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/DependentTaskAPITest.java @@ -122,7 +122,9 @@ public void testCreateUpstreamWorkflow() throws Exception { Assertions.assertNotEquals(0, projectCode, "project should be found by name"); log.info("project code: {}", projectCode); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // create upstream workflow definition (shell task: echo hello) ClassLoader classLoader = getClass().getClassLoader(); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java index a5169a4a052c..187bc32a2af5 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/EmrServerlessTaskAPITest.java @@ -106,7 +106,9 @@ public void testEmrServerlessSuccessWorkflowInstance() throws Exception { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java index 7175670d6ef8..5b3c6dbae7ee 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/tasks/GrpcTaskAPITest.java @@ -103,7 +103,9 @@ public void testGrpcFailedWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); @@ -155,7 +157,9 @@ public void testGrpcSuccessWorkflowInstance() { projectCode = (long) ((LinkedHashMap) ((List) queryAllProjectListResponse .getBody().getData()).get(0)).get("code"); - projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + HttpResponse assignWorkerGroupsResponse = + projectPage.assignWorkerGroups(loginUser, projectCode, new String[]{"default"}); + Assertions.assertTrue(assignWorkerGroupsResponse.getBody().getSuccess()); // upload test workflow definition json ClassLoader classLoader = getClass().getClassLoader(); From 951c41a6c51b2cfc1097c81e3de668cfe2c1552b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Wed, 22 Jul 2026 14:34:11 +0800 Subject: [PATCH 20/27] resolve element click intercepted error in testRemoveWorkerGroup by using explicit wait and JS click --- .../dolphinscheduler/e2e/pages/project/ProjectPage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java index d6573eb3b80c..14c36cf63e3e 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/project/ProjectPage.java @@ -169,7 +169,9 @@ public ProjectPage verifyRemovedWorkerGroup(String project, String workerGroup) throw new RuntimeException( "Worker group should have been deleted but still exists: " + workerGroup); }); - assignWorkerGroupForm.buttonCancel().click(); + + await().until(() -> assignWorkerGroupForm.buttonCancel().isEnabled()); + driver.executeScript("arguments[0].click();", assignWorkerGroupForm.buttonCancel()); return this; } From c78a7cfd70e7e5e247e629af6e98f2550cde3a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Wed, 22 Jul 2026 15:25:00 +0800 Subject: [PATCH 21/27] Fix form parameter parsing for POST/PUT requests in RequestClient --- .../api/test/utils/RequestClient.java | 59 +++++-------------- 1 file changed, 15 insertions(+), 44 deletions(-) diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java index eabde7de30dc..ee8fd0988cfd 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java +++ b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/utils/RequestClient.java @@ -21,19 +21,8 @@ import org.apache.dolphinscheduler.api.test.entity.HttpResponse; import org.apache.dolphinscheduler.api.test.entity.HttpResponseBody; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicHeader; - -import java.io.File; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; @@ -98,6 +87,10 @@ public HttpResponse get(String url, Map headers, Map params) { + return getParams(params, true); + } + + public static String getParams(Map params, boolean includeQuestionMark) { if (params == null || params.isEmpty()) { return ""; } @@ -122,12 +115,12 @@ public static String getParams(Map params) { for (int i = 0; i < length; i++) { Object item = java.lang.reflect.Array.get(value, i); if (item != null) { - appendParam(sb, isFirst, key, item.toString()); + appendParam(sb, isFirst, key, item.toString(), includeQuestionMark); isFirst = false; } } } else { - appendParam(sb, isFirst, key, value.toString()); + appendParam(sb, isFirst, key, value.toString(), includeQuestionMark); isFirst = false; } } @@ -135,9 +128,12 @@ public static String getParams(Map params) { return sb.toString(); } - private static void appendParam(StringBuilder sb, boolean isFirst, String key, String value) { + private static void appendParam(StringBuilder sb, boolean isFirst, String key, String value, + boolean includeQuestionMark) { if (isFirst) { - sb.append(Constants.QUESTION_MARK); + if (includeQuestionMark) { + sb.append(Constants.QUESTION_MARK); + } } else { sb.append(Constants.AND_MARK); } @@ -159,7 +155,8 @@ public HttpResponse post(String url, Map headers, Map headers, Map headers, Map headers, Map params, - File file) { - try { - Headers headersBuilder = Headers.of(headers); - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("json", getParams(params), ContentType.MULTIPART_FORM_DATA); - builder.addBinaryBody( - "file", - Files.newInputStream(file.toPath()), - ContentType.APPLICATION_OCTET_STREAM, - file.getName()); - HttpEntity multipart = builder.build(); - String requestUrl = String.format("%s%s", Constants.DOLPHINSCHEDULER_API_URL, url); - log.info("POST request to {}, Headers: {}, Params: {}", requestUrl, headersBuilder, params); - HttpPost httpPost = new HttpPost(requestUrl); - for (Map.Entry header : headers.entrySet()) { - httpPost.setHeader(new BasicHeader(header.getKey(), header.getValue())); - } - httpPost.setEntity(multipart); - CloseableHttpClient client = HttpClients.createDefault(); - return client.execute(httpPost); - } catch (Exception e) { - log.error("error", e); - } - return null; - } - @SneakyThrows public HttpResponse delete(String url, Map headers, Map params) { if (headers == null) { From 1d5f658772018cb612049b77e877d02327b0e560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Thu, 23 Jul 2026 19:15:36 +0800 Subject: [PATCH 22/27] ensure 'default' worker group exists before project creation in e2e test --- .../e2e/cases/WorkflowE2ETest.java | 12 +++++++++++- .../e2e/cases/WorkflowHttpTaskE2ETest.java | 12 +++++++++++- .../e2e/cases/WorkflowJavaTaskE2ETest.java | 12 +++++++++++- .../e2e/cases/WorkflowSwitchE2ETest.java | 12 +++++++++++- .../e2e/cases/tasks/PythonTaskE2ETest.java | 14 +++++++++++--- .../e2e/cases/tasks/ShellTaskE2ETest.java | 11 ++++++++++- 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java index 4895a501cd77..6e09d9b3d457 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowE2ETest.java @@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; import org.apache.dolphinscheduler.e2e.pages.security.UserPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -78,7 +79,16 @@ public static void setup() { WebDriverWaitFactory.createWebDriverWait(userPage.driver()).until(ExpectedConditions.visibilityOfElementLocated( new By.ByClassName("name"))); - userPage.update(user, user, email, phone, tenant) + WorkerGroupPage workerGroupPage = userPage.update(user, user, email, phone, tenant) + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } + + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(project); } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java index 3665a16be8a5..e99f50439a9f 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowHttpTaskE2ETest.java @@ -32,6 +32,7 @@ import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; import org.apache.dolphinscheduler.e2e.pages.security.UserPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -78,7 +79,16 @@ public static void setup() { WebDriverWaitFactory.createWebDriverWait(userPage.driver()).until(ExpectedConditions.visibilityOfElementLocated( new By.ByClassName("name"))); - userPage.update(user, user, email, phone, tenant) + WorkerGroupPage workerGroupPage = userPage.update(user, user, email, phone, tenant) + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } + + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(project); } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java index 26518f600b6e..30b848b045c9 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowJavaTaskE2ETest.java @@ -36,6 +36,7 @@ import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; import org.apache.dolphinscheduler.e2e.pages.security.UserPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import java.io.File; import java.io.FileOutputStream; @@ -206,7 +207,16 @@ public static void setup() { WebDriverWaitFactory.createWebDriverWait(userPage.driver()) .until(ExpectedConditions.visibilityOfElementLocated(new By.ByClassName("name"))); - userPage.update(user, user, email, phone, tenant) + WorkerGroupPage workerGroupPage = userPage.update(user, user, email, phone, tenant) + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } + + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(project); diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java index 6e7232e83da3..6f434f566fc6 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/WorkflowSwitchE2ETest.java @@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.e2e.pages.project.workflow.task.SwitchTaskForm; import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import java.util.List; @@ -60,11 +61,20 @@ class WorkflowSwitchE2ETest { @BeforeAll public static void setup() { - new LoginPage(browser) + WorkerGroupPage workerGroupPage = new LoginPage(browser) .login("admin", "dolphinscheduler123") .goToNav(SecurityPage.class) .goToTab(TenantPage.class) .create(tenant) + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } + + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(project); } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java index b89e371a1299..dce4beb02e2c 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/PythonTaskE2ETest.java @@ -36,6 +36,7 @@ import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; import org.apache.dolphinscheduler.e2e.pages.security.UserPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import java.util.Date; @@ -66,15 +67,22 @@ public static void setup() { .goToTab(UserPage.class) .update(adminUser); } - tenantPage + WorkerGroupPage workerGroupPage = tenantPage .goToNav(SecurityPage.class) .goToTab(EnvironmentPage.class) .createEnvironmentUntilSuccess(pythonEnvironment.getEnvironmentName(), pythonEnvironment.getEnvironmentConfig(), pythonEnvironment.getEnvironmentDesc(), - pythonEnvironment.getEnvironmentWorkerGroup()); + pythonEnvironment.getEnvironmentWorkerGroup()) + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } - tenantPage + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(projectName); } diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java index 181bc8d1df36..e1b5a2c7ad82 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java @@ -63,7 +63,16 @@ public static void setup() { .update(adminUser); } - tenantPage + WorkerGroupPage workerGroupPage = tenantPage + .goToNav(SecurityPage.class) + .goToTab(WorkerGroupPage.class); + + if (workerGroupPage.workerGroupList().stream() + .noneMatch(it -> it.getText().contains("default"))) { + workerGroupPage.create("default"); + } + + workerGroupPage .goToNav(ProjectPage.class) .createProjectUntilSuccess(projectName); } From c3459275aa9a4ded02e28f9fad17fa3bf35f00c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 10 Aug 2026 11:49:19 +0800 Subject: [PATCH 23/27] add import --- .../dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java index e1b5a2c7ad82..c3cfcb286251 100644 --- a/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java +++ b/dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/tasks/ShellTaskE2ETest.java @@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.e2e.pages.security.SecurityPage; import org.apache.dolphinscheduler.e2e.pages.security.TenantPage; import org.apache.dolphinscheduler.e2e.pages.security.UserPage; +import org.apache.dolphinscheduler.e2e.pages.security.WorkerGroupPage; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; From ba5199b44b3305a1bcb6508e100173ac2a0dae2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 7 Sep 2026 10:27:16 +0800 Subject: [PATCH 24/27] merge dev and resolve conflicts --- .../impl/WorkflowDefinitionServiceImpl.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java index 6cb7d6f55784..199d4f117a92 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java @@ -1764,7 +1764,7 @@ public void deleteWorkflowDefinitionVersion(User loginUser, throw new ServiceException(Status.MAIN_TABLE_USING_VERSION); } // check whether there exist running workflow instance under the workflow definition - List workflowInstances = workflowInstanceService.queryByWorkflowCodeVersionStatus( + List workflowInstances = workflowInstanceService.queryByWorkflowCodeVersionStatus( code, version, WorkflowExecutionStatus.NOT_TERMINAL_STATES); @@ -1784,17 +1784,20 @@ public void deleteWorkflowDefinitionVersion(User loginUser, @Transactional @Override public void onlineWorkflowDefinition(User loginUser, Long projectCode, Long workflowDefinitionCode) { - projectService.checkProjectAndAuthThrowException(loginUser, projectCode, WORKFLOW_ONLINE_OFFLINE); + projectService.checkHasProjectWritePermissionThrowException(loginUser, projectCode); WorkflowDefinition workflowDefinition = workflowDefinitionDao.queryByCode(workflowDefinitionCode) .orElseThrow(() -> new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode)); + if (projectCode != workflowDefinition.getProjectCode()) { + throw new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode); + } if (ReleaseState.ONLINE.equals(workflowDefinition.getReleaseState())) { // do nothing if the workflow is already online return; } - checkWorkflowDefinitionIsValidated(workflowDefinition.getCode()); + checkWorkflowDefinitionIsValidated(loginUser, workflowDefinition.getCode()); checkAllSubWorkflowDefinitionIsOnline(workflowDefinition.getCode()); workflowDefinition.setReleaseState(ReleaseState.ONLINE); @@ -1804,10 +1807,13 @@ public void onlineWorkflowDefinition(User loginUser, Long projectCode, Long work @Transactional @Override public void offlineWorkflowDefinition(User loginUser, Long projectCode, Long workflowDefinitionCode) { - projectService.checkProjectAndAuthThrowException(loginUser, projectCode, WORKFLOW_ONLINE_OFFLINE); + projectService.checkHasProjectWritePermissionThrowException(loginUser, projectCode); WorkflowDefinition workflowDefinition = workflowDefinitionDao.queryByCode(workflowDefinitionCode) .orElseThrow(() -> new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode)); + if (projectCode != workflowDefinition.getProjectCode()) { + throw new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode); + } if (ReleaseState.OFFLINE.equals(workflowDefinition.getReleaseState())) { // do nothing if the workflow is already offline @@ -1887,13 +1893,17 @@ private Map> getLocalParams(WorkflowDefinition workf return localUserDefParams; } - private void checkWorkflowDefinitionIsValidated(Long workflowDefinitionCode) { + private void checkWorkflowDefinitionIsValidated(User loginUser, Long workflowDefinitionCode) { // todo: build dag check if the dag is validated List workflowTaskRelations = workflowTaskRelationDao.queryByWorkflowDefinitionCode(workflowDefinitionCode); if (CollectionUtils.isEmpty(workflowTaskRelations)) { throw new ServiceException(Status.WORKFLOW_DAG_IS_EMPTY); } + List taskDefinitionLogs = + taskDefinitionLogDao.queryTaskDefineLogList(workflowTaskRelations); + taskDatasourcePermissionChecker.checkPermission(loginUser, taskDefinitionLogs); + taskSubWorkflowPermissionChecker.checkPermission(loginUser, taskDefinitionLogs); // todo : check Workflow is validate } From aa173fd4b5a227b68ffed77ffedb2855601bda16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 7 Sep 2026 13:43:55 +0800 Subject: [PATCH 25/27] Validate worker groups when copying, moving or going online a workflow definition --- .../impl/WorkflowDefinitionServiceImpl.java | 43 ++++++-- .../WorkflowDefinitionServiceTest.java | 104 ++++++++++++++++++ 2 files changed, 137 insertions(+), 10 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java index 199d4f117a92..e4624e684ab4 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java @@ -50,6 +50,7 @@ import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.validator.GlobalParamsValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidationContext; import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.ReleaseState; @@ -411,6 +412,17 @@ private void validateTaskWorkerGroups(long projectCode, List workerGroupValidator.validate(workerGroups, projectCode); } + /** + * Validate the worker group of a schedule against the project + */ + private void validateScheduleWorkerGroup(long projectCode, String workerGroup) { + WorkerGroupValidationContext workerGroupContext = WorkerGroupValidationContext.builder() + .workerGroup(workerGroup) + .projectCode(projectCode) + .build(); + workerGroupValidator.validate(workerGroupContext); + } + private List generateTaskRelationList(String taskRelationJson, List taskDefinitionLogs) { try { @@ -1386,6 +1398,8 @@ protected void doBatchOperateWorkflowDefinition(User loginUser, log.info("Copy workflow definition..."); List taskDefinitionLogs = taskDefinitionLogDao.queryTaskDefineLogList(workflowTaskRelations); + // Validate the worker groups of the copied tasks against the target project + validateTaskWorkerGroups(targetProjectCode, taskDefinitionLogs); Map taskCodeMap = new HashMap<>(); taskDefinitionLogs.forEach( taskDefinitionLog -> taskCodeMap.put(taskDefinitionLog.getCode(), CodeGenerateUtils.genCode())); @@ -1404,14 +1418,16 @@ protected void doBatchOperateWorkflowDefinition(User loginUser, } } - for (WorkflowTaskRelationLog workflowTaskRelationLog : taskRelationList) { - if (workflowTaskRelationLog.getPreTaskCode() > 0) { - workflowTaskRelationLog - .setPreTaskCode(taskCodeMap.get(workflowTaskRelationLog.getPreTaskCode())); - } - if (workflowTaskRelationLog.getPostTaskCode() > 0) { - workflowTaskRelationLog - .setPostTaskCode(taskCodeMap.get(workflowTaskRelationLog.getPostTaskCode())); + if (!taskDefinitionLogs.isEmpty()) { + for (WorkflowTaskRelationLog workflowTaskRelationLog : taskRelationList) { + if (workflowTaskRelationLog.getPreTaskCode() > 0) { + workflowTaskRelationLog + .setPreTaskCode(taskCodeMap.get(workflowTaskRelationLog.getPreTaskCode())); + } + if (workflowTaskRelationLog.getPostTaskCode() > 0) { + workflowTaskRelationLog + .setPostTaskCode(taskCodeMap.get(workflowTaskRelationLog.getPostTaskCode())); + } } } final long oldWorkflowDefinitionCode = workflowDefinition.getCode(); @@ -1436,6 +1452,7 @@ protected void doBatchOperateWorkflowDefinition(User loginUser, // copy timing configuration Schedule scheduleObj = scheduleDao.queryByWorkflowDefinitionCode(oldWorkflowDefinitionCode); if (scheduleObj != null) { + validateScheduleWorkerGroup(targetProjectCode, scheduleObj.getWorkerGroup()); scheduleObj.setId(null); scheduleObj.setUserId(loginUser.getId()); scheduleObj.setWorkflowDefinitionCode(workflowDefinition.getCode()); @@ -1457,6 +1474,10 @@ protected void doBatchOperateWorkflowDefinition(User loginUser, } } else { log.info("Move workflow definition..."); + List taskDefinitionLogs = + taskDefinitionLogDao.queryTaskDefineLogList(workflowTaskRelations); + // Validate the worker groups of the moved tasks against the target project + validateTaskWorkerGroups(targetProjectCode, taskDefinitionLogs); try { updateDagDefine(loginUser, taskRelationList, workflowDefinition, null, Lists.newArrayList()); @@ -1797,7 +1818,7 @@ public void onlineWorkflowDefinition(User loginUser, Long projectCode, Long work return; } - checkWorkflowDefinitionIsValidated(loginUser, workflowDefinition.getCode()); + checkWorkflowDefinitionIsValidated(loginUser, projectCode, workflowDefinition.getCode()); checkAllSubWorkflowDefinitionIsOnline(workflowDefinition.getCode()); workflowDefinition.setReleaseState(ReleaseState.ONLINE); @@ -1893,7 +1914,7 @@ private Map> getLocalParams(WorkflowDefinition workf return localUserDefParams; } - private void checkWorkflowDefinitionIsValidated(User loginUser, Long workflowDefinitionCode) { + private void checkWorkflowDefinitionIsValidated(User loginUser, Long projectCode, Long workflowDefinitionCode) { // todo: build dag check if the dag is validated List workflowTaskRelations = workflowTaskRelationDao.queryByWorkflowDefinitionCode(workflowDefinitionCode); @@ -1904,6 +1925,8 @@ private void checkWorkflowDefinitionIsValidated(User loginUser, Long workflowDef taskDefinitionLogDao.queryTaskDefineLogList(workflowTaskRelations); taskDatasourcePermissionChecker.checkPermission(loginUser, taskDefinitionLogs); taskSubWorkflowPermissionChecker.checkPermission(loginUser, taskDefinitionLogs); + // Validate the worker groups of the workflow tasks are assigned to the project + validateTaskWorkerGroups(projectCode, taskDefinitionLogs); // todo : check Workflow is validate } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java index 9cf0f622f53c..7aca6f93fbc7 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java @@ -41,6 +41,7 @@ import org.apache.dolphinscheduler.api.service.impl.WorkflowDefinitionServiceImpl; import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.validator.GlobalParamsValidator; +import org.apache.dolphinscheduler.api.validator.WorkerGroupValidationContext; import org.apache.dolphinscheduler.api.validator.WorkerGroupValidator; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; @@ -580,6 +581,109 @@ public void testBatchMoveWorkflowDefinitionRejectsDefinitionOutsideSourceProject verify(workflowTaskRelationDao, Mockito.never()).queryByWorkflowDefinitionCode(Mockito.anyLong()); } + @Test + public void testBatchCopyWorkflowDefinitionShouldRejectWorkerGroupNotAssignedToTargetProject() { + Project sourceProject = getProject(projectCode); + Project targetProject = getProject(projectCodeOther); + when(projectDao.queryByCode(projectCode)).thenReturn(sourceProject); + when(projectDao.queryByCode(projectCodeOther)).thenReturn(targetProject); + doNothing().when(projectService) + .checkProjectAndAuthThrowException(user, sourceProject, WORKFLOW_BATCH_COPY); + doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user, targetProject); + + when(workflowDefinitionDao.queryByCodes(Collections.singleton(processDefinitionCode))) + .thenReturn(Collections.singletonList(getWorkflowDefinition())); + when(workflowTaskRelationDao.queryByWorkflowDefinitionCode(processDefinitionCode)) + .thenReturn(getProcessTaskRelation()); + when(taskDefinitionLogDao.queryTaskDefineLogList(anyList())) + .thenReturn(Collections.singletonList(new TaskDefinitionLog())); + doThrow(new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT)) + .when(workerGroupValidator).validate(anyList(), anyLong()); + + ServiceException ex = Assertions.assertThrows(ServiceException.class, + () -> workflowDefinitionService.batchCopyWorkflowDefinition( + user, projectCode, String.valueOf(processDefinitionCode), projectCodeOther)); + + Assertions.assertEquals(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT.getCode(), ex.getCode()); + verify(workerGroupValidator).validate(anyList(), eq(projectCodeOther)); + verify(scheduleDao, Mockito.never()).insert(any(Schedule.class)); + } + + @Test + public void testBatchCopyWorkflowDefinitionShouldRejectScheduleWorkerGroupNotAssignedToTargetProject() { + Project sourceProject = getProject(projectCode); + Project targetProject = getProject(projectCodeOther); + when(projectDao.queryByCode(projectCode)).thenReturn(sourceProject); + when(projectDao.queryByCode(projectCodeOther)).thenReturn(targetProject); + doNothing().when(projectService) + .checkProjectAndAuthThrowException(user, sourceProject, WORKFLOW_BATCH_COPY); + doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user, targetProject); + + when(workflowDefinitionDao.queryByCodes(Collections.singleton(processDefinitionCode))) + .thenReturn(Collections.singletonList(getWorkflowDefinition())); + when(workflowTaskRelationDao.queryByWorkflowDefinitionCode(processDefinitionCode)) + .thenReturn(getProcessTaskRelation()); + when(taskDefinitionLogDao.queryTaskDefineLogList(anyList())).thenReturn(Collections.emptyList()); + when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(getSchedule()); + doThrow(new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT)) + .when(workerGroupValidator).validate(any(WorkerGroupValidationContext.class)); + + ServiceException ex = Assertions.assertThrows(ServiceException.class, + () -> workflowDefinitionService.batchCopyWorkflowDefinition( + user, projectCode, String.valueOf(processDefinitionCode), projectCodeOther)); + + Assertions.assertEquals(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT.getCode(), ex.getCode()); + verify(workerGroupValidator).validate(any(WorkerGroupValidationContext.class)); + verify(scheduleDao, Mockito.never()).insert(any(Schedule.class)); + } + + @Test + public void testBatchMoveWorkflowDefinitionShouldRejectWorkerGroupNotAssignedToTargetProject() { + Project sourceProject = getProject(projectCode); + Project targetProject = getProject(projectCodeOther); + when(projectDao.queryByCode(projectCode)).thenReturn(sourceProject); + when(projectDao.queryByCode(projectCodeOther)).thenReturn(targetProject); + doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user, sourceProject); + doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user, targetProject); + + when(workflowDefinitionDao.queryByCodes(Collections.singleton(processDefinitionCode))) + .thenReturn(Collections.singletonList(getWorkflowDefinition())); + when(workflowTaskRelationDao.queryByWorkflowDefinitionCode(processDefinitionCode)) + .thenReturn(getProcessTaskRelation()); + when(taskDefinitionLogDao.queryTaskDefineLogList(anyList())) + .thenReturn(Collections.singletonList(new TaskDefinitionLog())); + doThrow(new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT)) + .when(workerGroupValidator).validate(anyList(), anyLong()); + + ServiceException ex = Assertions.assertThrows(ServiceException.class, + () -> workflowDefinitionService.batchMoveWorkflowDefinition( + user, projectCode, String.valueOf(processDefinitionCode), projectCodeOther)); + + Assertions.assertEquals(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT.getCode(), ex.getCode()); + verify(workerGroupValidator).validate(anyList(), eq(projectCodeOther)); + } + + @Test + public void testOnlineWorkflowDefinitionShouldRejectWorkerGroupNotAssignedToProject() { + WorkflowDefinition workflowDefinition = getWorkflowDefinition(); + WorkflowTaskRelation workflowTaskRelation = + getWorkflowTaskRelation(1, 1, projectCode, processDefinitionCode, 0, 0, 123456789L, 1); + when(workflowDefinitionDao.queryByCode(processDefinitionCode)).thenReturn(Optional.of(workflowDefinition)); + when(workflowTaskRelationDao.queryByWorkflowDefinitionCode(processDefinitionCode)) + .thenReturn(Collections.singletonList(workflowTaskRelation)); + when(taskDefinitionLogDao.queryTaskDefineLogList(anyList())) + .thenReturn(Collections.singletonList(new TaskDefinitionLog())); + doThrow(new ServiceException(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT)) + .when(workerGroupValidator).validate(anyList(), anyLong()); + + ServiceException exception = Assertions.assertThrows(ServiceException.class, + () -> workflowDefinitionService.onlineWorkflowDefinition(user, projectCode, processDefinitionCode)); + + Assertions.assertEquals(Status.WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT.getCode(), exception.getCode()); + verify(workerGroupValidator).validate(anyList(), eq(projectCode)); + Mockito.verify(workflowDefinitionDao, Mockito.never()).updateById(any(WorkflowDefinition.class)); + } + @Test public void testReadOnlyUserCannotSwitchOrReleaseWorkflowDefinition() { Project project = getProject(projectCode); From 1dcb7c67a2adadb8c13b3de25578d7092312443c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 7 Sep 2026 14:01:16 +0800 Subject: [PATCH 26/27] Document the worker group assignment behavior change in the upgrade guide --- docs/docs/en/guide/upgrade/incompatible.md | 4 ++++ docs/docs/zh/guide/upgrade/incompatible.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/docs/en/guide/upgrade/incompatible.md b/docs/docs/en/guide/upgrade/incompatible.md index ad118a1d6088..31df40909a3c 100644 --- a/docs/docs/en/guide/upgrade/incompatible.md +++ b/docs/docs/en/guide/upgrade/incompatible.md @@ -55,4 +55,8 @@ This document records the incompatible updates between each version. You need to * **Removed transient fields**: `stateDescList`, `workflowDefinition`, `dagData`, `queue`, `locations`, `dependenceScheduleTimes` * **Removed derived properties**: `cmdTypeIfComplement`, `complementData` (related to complement-data executions; use the detail API to obtain them) * To obtain any of these fields, use the detail API `GET /projects/{projectCode}/workflow-instances/{id}` instead, which continues to return the full `WorkflowInstance` object. ([#18444](https://github.com/apache/dolphinscheduler/pull/18444)) +* Worker groups must now be assigned to a project before they can be used([#18293](https://github.com/apache/dolphinscheduler/pull/18293)): + * The frontend no longer provides a default `default` worker group for workflow definitions, run forms, or project preferences; the submitted value is `null` when none is selected. + * When creating or updating a workflow definition, creating or updating a schedule, updating project preferences, copying or moving a workflow definition into a project, or going a workflow definition online, the backend validates that the worker groups used by the tasks and the schedule are assigned to the relevant project. Unassigned worker groups cause the request to fail (error code `WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT`, 1402005). + * To avoid breaking existing data, worker groups explicitly assigned to a project, as well as worker groups **actually used** by tasks or schedules within the project, are still considered assigned; only scenarios that use new worker groups require attention. diff --git a/docs/docs/zh/guide/upgrade/incompatible.md b/docs/docs/zh/guide/upgrade/incompatible.md index a0f945501989..edce9c2d1ee9 100644 --- a/docs/docs/zh/guide/upgrade/incompatible.md +++ b/docs/docs/zh/guide/upgrade/incompatible.md @@ -55,4 +55,8 @@ * **移除的非数据库字段**:`stateDescList`、`workflowDefinition`、`dagData`、`queue`、`locations`、`dependenceScheduleTimes` * **移除的派生属性**:`cmdTypeIfComplement`、`complementData`(补数执行相关,如需获取请使用详情接口) * 如需获取这些字段,请使用详情接口 `GET /projects/{projectCode}/workflow-instances/{id}`,该接口仍返回完整的 `WorkflowInstance` 对象 ([#18444](https://github.com/apache/dolphinscheduler/pull/18444)) +* Worker 组(workerGroup)现在必须预先分配给项目后才能使用([#18293](https://github.com/apache/dolphinscheduler/pull/18293)): + * 前端不再为工作流定义、运行表单和项目偏好提供默认的 `default` worker 组,未指定时提交为 `null`。 + * 在创建或更新工作流定义、创建或更新定时、更新项目偏好、复制或移动工作流定义到其他项目、以及上线工作流时,后端会校验任务和定时使用的 worker 组是否已分配给相关项目;未分配的 worker 组将导致请求失败(错误码 `WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT`,1402005)。 + * 为避免破坏存量数据,已显式分配给项目的 worker 组、以及被项目内任务或定时**实际使用**的 worker 组,仍视为已分配;仅需关注使用新 worker 组的场景。 From c670b3ba9d4004327b3921562a11d978f6e3c9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E4=B9=89=E8=B6=85?= Date: Mon, 7 Sep 2026 14:18:30 +0800 Subject: [PATCH 27/27] update version --- docs/docs/en/guide/upgrade/incompatible.md | 3 +++ docs/docs/zh/guide/upgrade/incompatible.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/docs/en/guide/upgrade/incompatible.md b/docs/docs/en/guide/upgrade/incompatible.md index 31df40909a3c..05a6ae6ccab8 100644 --- a/docs/docs/en/guide/upgrade/incompatible.md +++ b/docs/docs/en/guide/upgrade/incompatible.md @@ -55,6 +55,9 @@ This document records the incompatible updates between each version. You need to * **Removed transient fields**: `stateDescList`, `workflowDefinition`, `dagData`, `queue`, `locations`, `dependenceScheduleTimes` * **Removed derived properties**: `cmdTypeIfComplement`, `complementData` (related to complement-data executions; use the detail API to obtain them) * To obtain any of these fields, use the detail API `GET /projects/{projectCode}/workflow-instances/{id}` instead, which continues to return the full `WorkflowInstance` object. ([#18444](https://github.com/apache/dolphinscheduler/pull/18444)) + +## 3.5.0 + * Worker groups must now be assigned to a project before they can be used([#18293](https://github.com/apache/dolphinscheduler/pull/18293)): * The frontend no longer provides a default `default` worker group for workflow definitions, run forms, or project preferences; the submitted value is `null` when none is selected. * When creating or updating a workflow definition, creating or updating a schedule, updating project preferences, copying or moving a workflow definition into a project, or going a workflow definition online, the backend validates that the worker groups used by the tasks and the schedule are assigned to the relevant project. Unassigned worker groups cause the request to fail (error code `WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT`, 1402005). diff --git a/docs/docs/zh/guide/upgrade/incompatible.md b/docs/docs/zh/guide/upgrade/incompatible.md index edce9c2d1ee9..bd12d68b9536 100644 --- a/docs/docs/zh/guide/upgrade/incompatible.md +++ b/docs/docs/zh/guide/upgrade/incompatible.md @@ -55,6 +55,9 @@ * **移除的非数据库字段**:`stateDescList`、`workflowDefinition`、`dagData`、`queue`、`locations`、`dependenceScheduleTimes` * **移除的派生属性**:`cmdTypeIfComplement`、`complementData`(补数执行相关,如需获取请使用详情接口) * 如需获取这些字段,请使用详情接口 `GET /projects/{projectCode}/workflow-instances/{id}`,该接口仍返回完整的 `WorkflowInstance` 对象 ([#18444](https://github.com/apache/dolphinscheduler/pull/18444)) + +## 3.5.0 + * Worker 组(workerGroup)现在必须预先分配给项目后才能使用([#18293](https://github.com/apache/dolphinscheduler/pull/18293)): * 前端不再为工作流定义、运行表单和项目偏好提供默认的 `default` worker 组,未指定时提交为 `null`。 * 在创建或更新工作流定义、创建或更新定时、更新项目偏好、复制或移动工作流定义到其他项目、以及上线工作流时,后端会校验任务和定时使用的 worker 组是否已分配给相关项目;未分配的 worker 组将导致请求失败(错误码 `WORKER_GROUP_NOT_ASSIGNED_TO_PROJECT`,1402005)。