Skip to content

Commit 0ef6e5f

Browse files
authored
Merge pull request #4357 from jsonwan/issue_4247
feat: 提供创建执行方案的OpenAPI V4接口 #4247
2 parents ed94968 + 85f6dd9 commit 0ef6e5f

3 files changed

Lines changed: 123 additions & 21 deletions

File tree

src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/api/esb/impl/v4/OpenApiJobPlanV4ResourceImpl.java

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@
3737
import com.tencent.bk.job.common.metrics.CommonMetricNames;
3838
import com.tencent.bk.job.common.model.User;
3939
import com.tencent.bk.job.common.model.dto.AppResourceScope;
40-
import com.tencent.bk.job.common.model.dto.ResourceScope;
41-
import com.tencent.bk.job.common.service.AppScopeMappingService;
42-
import com.tencent.bk.job.common.util.JobContextUtil;
4340
import com.tencent.bk.job.common.model.dto.ApplicationHostDTO;
41+
import com.tencent.bk.job.common.model.dto.ResourceScope;
4442
import com.tencent.bk.job.common.model.openapi.v3.EsbCmdbTopoNodeDTO;
4543
import com.tencent.bk.job.common.model.openapi.v3.EsbDynamicGroupDTO;
44+
import com.tencent.bk.job.common.service.AppScopeMappingService;
45+
import com.tencent.bk.job.common.util.JobContextUtil;
4646
import com.tencent.bk.job.common.util.date.DateUtils;
4747
import com.tencent.bk.job.execute.model.esb.v4.req.OpenApiV4HostDTO;
4848
import com.tencent.bk.job.execute.model.esb.v4.req.V4ExecuteTargetDTO;
@@ -59,6 +59,7 @@
5959
import com.tencent.bk.job.manage.model.esb.v4.OpenApiV4JobPlanDTO;
6060
import com.tencent.bk.job.manage.model.esb.v4.req.V4CreateJobPlanRequest;
6161
import com.tencent.bk.job.manage.model.esb.v4.req.V4JobPlanVariableItem;
62+
import com.tencent.bk.job.manage.service.host.TenantHostService;
6263
import com.tencent.bk.job.manage.service.plan.TaskPlanService;
6364
import com.tencent.bk.job.manage.service.template.TaskTemplateService;
6465
import org.apache.commons.collections4.CollectionUtils;
@@ -83,18 +84,21 @@ public class OpenApiJobPlanV4ResourceImpl implements OpenApiJobPlanV4Resource {
8384
private final TemplateAuthService templateAuthService;
8485
private final PlanAuthService planAuthService;
8586
private final AppScopeMappingService appScopeMappingService;
87+
private final TenantHostService tenantHostService;
8688

8789
@Autowired
8890
public OpenApiJobPlanV4ResourceImpl(TaskPlanService planService,
8991
TaskTemplateService templateService,
9092
TemplateAuthService templateAuthService,
9193
PlanAuthService planAuthService,
92-
AppScopeMappingService appScopeMappingService) {
94+
AppScopeMappingService appScopeMappingService,
95+
TenantHostService tenantHostService) {
9396
this.planService = planService;
9497
this.templateService = templateService;
9598
this.templateAuthService = templateAuthService;
9699
this.planAuthService = planAuthService;
97100
this.appScopeMappingService = appScopeMappingService;
101+
this.tenantHostService = tenantHostService;
98102
}
99103

100104
@Override
@@ -119,7 +123,7 @@ public EsbV4Response<OpenApiV4JobPlanDTO> createJobPlan(String username,
119123
}
120124

121125
List<Long> enableSteps = resolveEnableSteps(request, template);
122-
List<TaskVariableDTO> variableList = mapVariables(request.getVariables(), template);
126+
List<TaskVariableDTO> variableList = mapVariables(request.getVariables(), template, user.getTenantId());
123127

124128
String planName = StringUtils.strip(request.getName());
125129
if (Boolean.FALSE.equals(
@@ -162,7 +166,9 @@ private List<Long> resolveEnableSteps(V4CreateJobPlanRequest request, TaskTempla
162166
return new ArrayList<>(request.getEnableSteps());
163167
}
164168

165-
private List<TaskVariableDTO> mapVariables(List<V4JobPlanVariableItem> variables, TaskTemplateInfoDTO template) {
169+
private List<TaskVariableDTO> mapVariables(List<V4JobPlanVariableItem> variables,
170+
TaskTemplateInfoDTO template,
171+
String tenantId) {
166172
if (CollectionUtils.isEmpty(variables)) {
167173
return new ArrayList<>();
168174
}
@@ -220,7 +226,7 @@ private List<TaskVariableDTO> mapVariables(List<V4JobPlanVariableItem> variables
220226
}
221227
);
222228
}
223-
TaskTargetDTO taskTargetDTO = buildTaskTargetDTO(item.getExecuteTarget());
229+
TaskTargetDTO taskTargetDTO = buildTaskTargetDTO(item.getExecuteTarget(), tenantId);
224230
dto.setDefaultValue(taskTargetDTO.toJsonString());
225231
}
226232
} else if (!item.isFollowTemplate() && item.getValue() != null) {
@@ -232,7 +238,7 @@ private List<TaskVariableDTO> mapVariables(List<V4JobPlanVariableItem> variables
232238
}
233239

234240
/** 执行目标变量覆盖:仅主机维度,容器 filter 暂不支持。 */
235-
private TaskTargetDTO buildTaskTargetDTO(V4ExecuteTargetDTO v4) {
241+
private TaskTargetDTO buildTaskTargetDTO(V4ExecuteTargetDTO v4, String tenantId) {
236242
if (v4 == null) {
237243
throw new InvalidParamException(
238244
ErrorCode.ILLEGAL_PARAM_WITH_PARAM_NAME_AND_REASON,
@@ -265,9 +271,7 @@ private TaskTargetDTO buildTaskTargetDTO(V4ExecuteTargetDTO v4) {
265271
}
266272
TaskHostNodeDTO hostNode = new TaskHostNodeDTO();
267273
if (CollectionUtils.isNotEmpty(v4.getHostList())) {
268-
hostNode.setHostList(v4.getHostList().stream()
269-
.map(OpenApiJobPlanV4ResourceImpl::toApplicationHostDTO)
270-
.collect(Collectors.toList()));
274+
hostNode.setHostList(resolveHostList(v4.getHostList(), tenantId));
271275
}
272276
if (CollectionUtils.isNotEmpty(v4.getDynamicGroups())) {
273277
hostNode.setDynamicGroupId(v4.getDynamicGroups().stream()
@@ -282,15 +286,59 @@ private TaskTargetDTO buildTaskTargetDTO(V4ExecuteTargetDTO v4) {
282286
return new TaskTargetDTO(null, hostNode, null);
283287
}
284288

285-
private static ApplicationHostDTO toApplicationHostDTO(OpenApiV4HostDTO host) {
286-
ApplicationHostDTO applicationHost = new ApplicationHostDTO();
287-
if (host.getBkHostId() != null) {
288-
applicationHost.setHostId(host.getBkHostId());
289-
} else {
290-
applicationHost.setCloudAreaId(host.getBkCloudId());
291-
applicationHost.setIp(host.getIp());
289+
/**
290+
* 将 OpenAPI 主机列表解析为 {@link ApplicationHostDTO} 列表,并补全 hostId。
291+
*
292+
* <p>已带 bk_host_id 的直接使用;仅传 bk_cloud_id+ip 的批量从 CMDB(经 TenantHostService 缓存兜底)反查 hostId。
293+
* 未能解析到 hostId 的主机会抛 {@link InvalidParamException},避免创建出页面回显"主机无效"的执行方案。
294+
*
295+
* @param hosts 入参主机列表,已由 {@link com.tencent.bk.job.execute.model.esb.v4.req.validator.V4HostGroupSequenceProvider}
296+
* 保证至少含有 bk_host_id 或 bk_cloud_id+ip
297+
* @param tenantId 当前请求租户 ID
298+
* @return 已补全 hostId 的主机列表
299+
*/
300+
private List<ApplicationHostDTO> resolveHostList(List<OpenApiV4HostDTO> hosts, String tenantId) {
301+
List<ApplicationHostDTO> result = new ArrayList<>(hosts.size());
302+
Set<String> cloudIpsToResolve = new HashSet<>();
303+
for (OpenApiV4HostDTO host : hosts) {
304+
ApplicationHostDTO dto = new ApplicationHostDTO();
305+
if (host.getBkHostId() != null) {
306+
dto.setHostId(host.getBkHostId());
307+
} else {
308+
dto.setCloudAreaId(host.getBkCloudId());
309+
dto.setIp(host.getIp());
310+
cloudIpsToResolve.add(dto.getCloudIp());
311+
}
312+
result.add(dto);
292313
}
293-
return applicationHost;
314+
if (cloudIpsToResolve.isEmpty()) {
315+
return result;
316+
}
317+
318+
Map<String, ApplicationHostDTO> hostsFromCmdb =
319+
tenantHostService.listHostsByIps(tenantId, cloudIpsToResolve);
320+
List<String> missingCloudIps = new ArrayList<>();
321+
for (ApplicationHostDTO dto : result) {
322+
if (dto.getHostId() != null) {
323+
continue;
324+
}
325+
ApplicationHostDTO cmdbHost = hostsFromCmdb == null ? null : hostsFromCmdb.get(dto.getCloudIp());
326+
if (cmdbHost == null || cmdbHost.getHostId() == null) {
327+
missingCloudIps.add(dto.getCloudIp());
328+
continue;
329+
}
330+
dto.setHostId(cmdbHost.getHostId());
331+
}
332+
if (!missingCloudIps.isEmpty()) {
333+
throw new InvalidParamException(
334+
ErrorCode.ILLEGAL_PARAM_WITH_PARAM_NAME_AND_REASON,
335+
new Object[]{
336+
"variables[].execute_target.host_list",
337+
"host not found in cmdb by cloud_id+ip: " + String.join(",", missingCloudIps)
338+
}
339+
);
340+
}
341+
return result;
294342
}
295343

296344
private static TaskNodeInfoDTO toTaskNodeInfoDTO(EsbCmdbTopoNodeDTO topoNode) {

src/backend/job-manage/service-job-manage/src/main/java/com/tencent/bk/job/manage/service/host/TenantHostService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public interface TenantHostService {
7171
*
7272
* @param tenantId 租户ID
7373
* @param cloudIps 主机云区域+ip列表
74-
* @return 主机 Map<hostId, host>
74+
* @return 主机 Map<cloudIp, host>
7575
*/
7676
Map<String, ApplicationHostDTO> listHostsByIps(String tenantId, Collection<String> cloudIps);
7777

src/backend/job-manage/service-job-manage/src/test/java/com/tencent/bk/job/manage/api/esb/v4/impl/OpenApiJobPlanV4ResourceImplTest.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.tencent.bk.job.common.iam.model.AuthResult;
3535
import com.tencent.bk.job.common.model.User;
3636
import com.tencent.bk.job.common.model.dto.AppResourceScope;
37+
import com.tencent.bk.job.common.model.dto.ApplicationHostDTO;
3738
import com.tencent.bk.job.common.model.dto.ResourceScope;
3839
import com.tencent.bk.job.common.model.openapi.v3.EsbCmdbTopoNodeDTO;
3940
import com.tencent.bk.job.common.model.openapi.v3.EsbDynamicGroupDTO;
@@ -53,6 +54,7 @@
5354
import com.tencent.bk.job.manage.model.esb.v4.OpenApiV4JobPlanDTO;
5455
import com.tencent.bk.job.manage.model.esb.v4.req.V4CreateJobPlanRequest;
5556
import com.tencent.bk.job.manage.model.esb.v4.req.V4JobPlanVariableItem;
57+
import com.tencent.bk.job.manage.service.host.TenantHostService;
5658
import com.tencent.bk.job.manage.service.plan.TaskPlanService;
5759
import com.tencent.bk.job.manage.service.template.TaskTemplateService;
5860
import org.junit.jupiter.api.AfterEach;
@@ -63,8 +65,11 @@
6365

6466
import java.util.ArrayList;
6567
import java.util.Arrays;
68+
import java.util.Collection;
6669
import java.util.Collections;
70+
import java.util.HashMap;
6771
import java.util.List;
72+
import java.util.Map;
6873

6974
import static org.assertj.core.api.Assertions.assertThat;
7075
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -95,6 +100,7 @@ class OpenApiJobPlanV4ResourceImplTest {
95100
private TemplateAuthService templateAuthService;
96101
private PlanAuthService planAuthService;
97102
private AppScopeMappingService appScopeMappingService;
103+
private TenantHostService tenantHostService;
98104

99105
private OpenApiJobPlanV4ResourceImpl resource;
100106
private User testUser;
@@ -106,6 +112,7 @@ void setUp() {
106112
templateAuthService = mock(TemplateAuthService.class);
107113
planAuthService = mock(PlanAuthService.class);
108114
appScopeMappingService = mock(AppScopeMappingService.class);
115+
tenantHostService = mock(TenantHostService.class);
109116
testUser = new User(TENANT_ID, USERNAME, USERNAME);
110117

111118
when(appScopeMappingService.getAppIdByScope(SCOPE_TYPE, SCOPE_ID)).thenReturn(APP_ID);
@@ -122,7 +129,8 @@ void setUp() {
122129
templateService,
123130
templateAuthService,
124131
planAuthService,
125-
appScopeMappingService
132+
appScopeMappingService,
133+
tenantHostService
126134
);
127135
JobContextUtil.setUser(testUser);
128136
}
@@ -354,6 +362,7 @@ void executeTarget_validation_and_mapping() {
354362
mixed.setDynamicGroups(Collections.singletonList(group));
355363
mixed.setTopoNodes(Collections.singletonList(topo));
356364

365+
stubListHostsByIps(Collections.singletonMap("0:127.0.0.1", buildCmdbHost(20002L, 0L, "127.0.0.1")));
357366
createWithExecuteTarget(mixed);
358367

359368
ArgumentCaptor<TaskPlanInfoDTO> captor = ArgumentCaptor.forClass(TaskPlanInfoDTO.class);
@@ -363,12 +372,57 @@ void executeTarget_validation_and_mapping() {
363372
);
364373
assertThat(target.getHostNodeList().getHostList()).hasSize(2);
365374
assertThat(target.getHostNodeList().getHostList().get(0).getHostId()).isEqualTo(10001L);
375+
assertThat(target.getHostNodeList().getHostList().get(1).getHostId()).isEqualTo(20002L);
366376
assertThat(target.getHostNodeList().getHostList().get(1).getIp()).isEqualTo("127.0.0.1");
367377
assertThat(target.getHostNodeList().getDynamicGroupId()).containsExactly("dg-001");
368378
assertThat(target.getHostNodeList().getNodeInfoList()).hasSize(1);
369379
assertThat(target.getHostNodeList().getNodeInfoList().get(0).getType()).isEqualTo("module");
370380
}
371381

382+
@Test
383+
@DisplayName("仅传 cloud+ip 且 CMDB 查不到主机时抛 ILLEGAL_PARAM,且不落库")
384+
void executeTarget_hostByIp_notFoundInCmdb_throws() {
385+
TaskVariableDTO hostVar = buildTemplateVar(20L, "HOST_TARGET", TaskVariableTypeEnum.EXECUTE_OBJECT_LIST, null);
386+
stubTemplateAndCreate(Collections.singletonList(101L), Collections.singletonList(hostVar));
387+
388+
OpenApiV4HostDTO hostByIp = new OpenApiV4HostDTO();
389+
hostByIp.setBkCloudId(0L);
390+
hostByIp.setIp("127.0.0.99");
391+
V4ExecuteTargetDTO executeTarget = new V4ExecuteTargetDTO();
392+
executeTarget.setHostList(Collections.singletonList(hostByIp));
393+
394+
// CMDB 返回空 -> 视为该 cloud+ip 主机不存在
395+
stubListHostsByIps(Collections.emptyMap());
396+
397+
assertThatThrownBy(() -> createWithExecuteTarget(executeTarget))
398+
.isInstanceOfSatisfying(InvalidParamException.class, e ->
399+
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.ILLEGAL_PARAM_WITH_PARAM_NAME_AND_REASON)
400+
);
401+
verify(planService, times(0)).createTaskPlan(any(User.class), any(TaskPlanInfoDTO.class));
402+
}
403+
404+
private void stubListHostsByIps(Map<String, ApplicationHostDTO> mapping) {
405+
when(tenantHostService.listHostsByIps(eq(TENANT_ID), any())).thenAnswer(invocation -> {
406+
Collection<String> requested = invocation.getArgument(1);
407+
Map<String, ApplicationHostDTO> result = new HashMap<>();
408+
for (String cloudIp : requested) {
409+
ApplicationHostDTO host = mapping.get(cloudIp);
410+
if (host != null) {
411+
result.put(cloudIp, host);
412+
}
413+
}
414+
return result;
415+
});
416+
}
417+
418+
private static ApplicationHostDTO buildCmdbHost(Long hostId, Long cloudId, String ip) {
419+
ApplicationHostDTO host = new ApplicationHostDTO();
420+
host.setHostId(hostId);
421+
host.setCloudAreaId(cloudId);
422+
host.setIp(ip);
423+
return host;
424+
}
425+
372426
private void createWithExecuteTarget(V4ExecuteTargetDTO executeTarget) {
373427
V4JobPlanVariableItem item = new V4JobPlanVariableItem();
374428
item.setName("HOST_TARGET");

0 commit comments

Comments
 (0)