feat(接口测试): 添加执行队列
This commit is contained in:
parent
8c2522ab26
commit
0df503b683
|
@ -0,0 +1,58 @@
|
|||
package io.metersphere.sdk.dto.queue;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ExecutionQueue implements Serializable {
|
||||
/**
|
||||
* 队列ID, 用于区分不同的队列,如果测试计划执行,队列ID为测试计划报告ID
|
||||
*/
|
||||
private String queueId;
|
||||
|
||||
/**
|
||||
* 报告类型/测试计划类型/测试用例类型/测试集类型/场景集合类型
|
||||
*/
|
||||
private String reportType;
|
||||
|
||||
/**
|
||||
* 运行模式
|
||||
*/
|
||||
private String runMode;
|
||||
|
||||
/**
|
||||
* 资源池ID
|
||||
*/
|
||||
private String poolId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Long createTime;
|
||||
|
||||
/**
|
||||
* 是否失败继续
|
||||
*/
|
||||
private Boolean failure;
|
||||
|
||||
/**
|
||||
* 开启重试
|
||||
*/
|
||||
private Boolean retryEnable;
|
||||
|
||||
/**
|
||||
* 重试次数
|
||||
*/
|
||||
private Long retryNumber;
|
||||
|
||||
/**
|
||||
* 环境,key= projectID,value=envID
|
||||
*/
|
||||
private Map<String, String> envMap;
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.metersphere.sdk.dto.queue;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ExecutionQueueDetail implements Serializable {
|
||||
/**
|
||||
* 资源id,每个资源在同一个运行队列中唯一
|
||||
*/
|
||||
private String resourceId;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 当前资源产生的执行报告id
|
||||
*/
|
||||
private String reportId;
|
||||
|
||||
/**
|
||||
* 资源类型 / API,CASE,PLAN_CASE,PLAN_SCENARIO,API_SCENARIO
|
||||
*/
|
||||
private String resourceType;
|
||||
|
||||
/**
|
||||
* 环境,key= projectID,value=envID,优先使用Queue上的环境,如果没有则使用资源上的环境
|
||||
*/
|
||||
private Map<String, String> envMap;
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package io.metersphere.api.service.queue;
|
||||
|
||||
import io.metersphere.sdk.dto.queue.ExecutionQueue;
|
||||
import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.ListOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ApiExecutionQueueService {
|
||||
|
||||
public static final String QUEUE_PREFIX = "queue:";
|
||||
public static final String QUEUE_DETAIL_PREFIX = "queue:detail:";
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void insertQueue(ExecutionQueue queue, List<ExecutionQueueDetail> queues) {
|
||||
// 保存队列信息
|
||||
redisTemplate.opsForValue().setIfAbsent(QUEUE_PREFIX + queue.getQueueId(), JSON.toJSONString(queue));
|
||||
// 保存队列详情信息
|
||||
queues.forEach(n -> redisTemplate.opsForList().rightPush(QUEUE_DETAIL_PREFIX + queue.getQueueId(), JSON.toJSONString(n)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一个节点
|
||||
*/
|
||||
public ExecutionQueueDetail getNextDetail(String queueId) throws Exception {
|
||||
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
|
||||
ListOperations<String, String> listOps = redisTemplate.opsForList();
|
||||
String queueDetail = listOps.leftPop(queueKey);
|
||||
if (StringUtils.isBlank(queueDetail)) {
|
||||
// 重试3次获取
|
||||
for (int i = 0; i < 3; i++) {
|
||||
queueDetail = redisTemplate.opsForList().leftPop(queueKey);
|
||||
if (StringUtils.isNotBlank(queueDetail)) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(queueDetail)) {
|
||||
// 将节点重新放回列表尾部,实现轮询
|
||||
return JSON.parseObject(queueDetail, ExecutionQueueDetail.class);
|
||||
}
|
||||
|
||||
// 整体获取完,清理队列
|
||||
redisTemplate.delete(queueKey);
|
||||
redisTemplate.delete(QUEUE_PREFIX + queueId);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节点
|
||||
*/
|
||||
public List<ExecutionQueueDetail> getDetails(String queueId) {
|
||||
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
|
||||
List<ExecutionQueueDetail> details = new LinkedList<>();
|
||||
ListOperations<String, String> listOps = redisTemplate.opsForList();
|
||||
Long listSize = listOps.size(queueKey);
|
||||
if (listSize == null) {
|
||||
return details;
|
||||
}
|
||||
|
||||
for (int i = 0; i < listSize; i++) {
|
||||
String element = listOps.index(queueKey, i);
|
||||
details.add(JSON.parseObject(element, ExecutionQueueDetail.class));
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列信息
|
||||
*/
|
||||
public ExecutionQueue getQueue(String queueId) {
|
||||
String queue = redisTemplate.opsForValue().get(QUEUE_PREFIX + queueId);
|
||||
if (StringUtils.isNotBlank(queue)) {
|
||||
return JSON.parseObject(queue, ExecutionQueue.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Long size(String queueId) {
|
||||
ListOperations<String, String> listOps = redisTemplate.opsForList();
|
||||
|
||||
String queueKey = QUEUE_DETAIL_PREFIX + queueId;
|
||||
return listOps.size(queueKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.service.queue.ApiExecutionQueueService;
|
||||
import io.metersphere.sdk.dto.queue.ExecutionQueue;
|
||||
import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.redis.core.ListOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class ApiExecutionQueueServiceTest {
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Mock
|
||||
private ListOperations<String, String> listOps;
|
||||
|
||||
@Resource
|
||||
private ApiExecutionQueueService apiExecutionQueueService;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void testInsertQueue() {
|
||||
ExecutionQueue queue = new ExecutionQueue();
|
||||
queue.setQueueId("queueId1");
|
||||
queue.setReportType("REPORT");
|
||||
queue.setRunMode("SEQUENTIAL");
|
||||
queue.setPoolId("poolId1");
|
||||
queue.setCreateTime(System.currentTimeMillis());
|
||||
queue.setFailure(true);
|
||||
queue.setRetryEnable(true);
|
||||
queue.setRetryNumber(3L);
|
||||
queue.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
|
||||
|
||||
ExecutionQueueDetail queueDetail1 = new ExecutionQueueDetail();
|
||||
queueDetail1.setResourceId("resourceId1");
|
||||
queueDetail1.setSort(1);
|
||||
queueDetail1.setReportId("reportId1");
|
||||
queueDetail1.setResourceType("API");
|
||||
queueDetail1.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
|
||||
|
||||
ExecutionQueueDetail queueDetail2 = new ExecutionQueueDetail();
|
||||
queueDetail2.setResourceId("resourceId2");
|
||||
queueDetail2.setSort(2);
|
||||
queueDetail2.setReportId("reportId2");
|
||||
queueDetail2.setResourceType("CASE");
|
||||
queueDetail2.setEnvMap(Map.of("projectID1", "envID1", "projectID2", "envID2"));
|
||||
|
||||
List<ExecutionQueueDetail> queueDetails = List.of(queueDetail1, queueDetail2);
|
||||
|
||||
when(redisTemplate.opsForList()).thenReturn(listOps);
|
||||
|
||||
apiExecutionQueueService.insertQueue(queue, queueDetails);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void testGetNextDetail() throws Exception {
|
||||
String queueId = "queueId1";
|
||||
System.out.println("start : " + apiExecutionQueueService.size(queueId));
|
||||
|
||||
when(redisTemplate.opsForList()).thenReturn(listOps);
|
||||
when(listOps.leftPop(queueId)).thenReturn("{\"resourceId\":\"resourceId1\"}");
|
||||
|
||||
ExecutionQueueDetail result = apiExecutionQueueService.getNextDetail(queueId);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("resourceId1", result.getResourceId());
|
||||
|
||||
System.out.println("end : " + apiExecutionQueueService.size(queueId));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
void testGetDetails() throws Exception {
|
||||
String queueId = "queueId1";
|
||||
|
||||
when(redisTemplate.opsForList()).thenReturn(listOps);
|
||||
when(listOps.size(queueId)).thenReturn(2L);
|
||||
when(listOps.index(eq(queueId), anyLong())).thenReturn("{\"resourceId\":\"resourceId1\"}", "{\"resourceId\":\"resourceId2\"}");
|
||||
|
||||
List<ExecutionQueueDetail> result = apiExecutionQueueService.getDetails(queueId);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
void testGetQueue() throws Exception {
|
||||
String queueId = "queueId1";
|
||||
ExecutionQueue result = apiExecutionQueueService.getQueue(queueId);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("queueId1", result.getQueueId());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package io.metersphere.api.config;
|
||||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.dto.NodeDTO;
|
||||
import io.metersphere.api.service.RoundRobinService;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
Loading…
Reference in New Issue