feat(性能测试-xPack): 增加k8s资源池

This commit is contained in:
Captain.B 2020-12-14 11:35:30 +08:00
parent 0bd4675886
commit e071f33dbd
8 changed files with 180 additions and 71 deletions

View File

@ -4,5 +4,5 @@ public enum ResourcePoolTypeEnum {
/**
* node controller 资源池
*/
NODE
NODE, K8S
}

View File

@ -2,7 +2,6 @@ package io.metersphere.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
@ -14,7 +13,6 @@ import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("testresourcepool")

View File

@ -0,0 +1,7 @@
package io.metersphere.service;
import io.metersphere.dto.TestResourcePoolDTO;
public interface KubernetesResourcePoolService {
boolean validate(TestResourcePoolDTO testResourcePool);
}

View File

@ -0,0 +1,96 @@
package io.metersphere.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestResource;
import io.metersphere.base.domain.TestResourceExample;
import io.metersphere.base.mapper.TestResourceMapper;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.NodeDTO;
import io.metersphere.dto.TestResourcePoolDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
@Service
public class NodeResourcePoolService {
private final static String nodeControllerUrl = "http://%s:%s/status";
@Resource
private RestTemplate restTemplateWithTimeOut;
@Resource
private TestResourceMapper testResourceMapper;
public boolean validate(TestResourcePoolDTO testResourcePool) {
if (CollectionUtils.isEmpty(testResourcePool.getResources())) {
MSException.throwException(Translator.get("no_nodes_message"));
}
deleteTestResource(testResourcePool.getId());
List<String> nodeIps = testResourcePool.getResources().stream()
.map(resource -> {
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
return nodeDTO.getIp();
})
.distinct()
.collect(Collectors.toList());
if (nodeIps.size() < testResourcePool.getResources().size()) {
MSException.throwException(Translator.get("duplicate_node_ip"));
}
testResourcePool.setStatus(VALID.name());
boolean isValid = true;
for (TestResource resource : testResourcePool.getResources()) {
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
boolean isValidate = validateNode(nodeDTO);
if (!isValidate) {
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
resource.setStatus(ResourceStatusEnum.INVALID.name());
isValid = false;
} else {
resource.setStatus(VALID.name());
}
resource.setTestResourcePoolId(testResourcePool.getId());
updateTestResource(resource);
}
return isValid;
}
private boolean validateNode(NodeDTO node) {
try {
ResponseEntity<String> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class);
return HttpStatus.OK.equals(entity.getStatusCode());
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
return false;
}
}
private void updateTestResource(TestResource testResource) {
testResource.setUpdateTime(System.currentTimeMillis());
testResource.setCreateTime(System.currentTimeMillis());
if (StringUtils.isBlank(testResource.getId())) {
testResource.setId(UUID.randomUUID().toString());
}
// 如果是更新操作插入与原来的ID相同的数据
testResourceMapper.insertSelective(testResource);
}
private void deleteTestResource(String testResourcePoolId) {
TestResourceExample testResourceExample = new TestResourceExample();
testResourceExample.createCriteria().andTestResourcePoolIdEqualTo(testResourcePoolId);
testResourceMapper.deleteByExample(testResourceExample);
}
}

View File

@ -1,26 +1,20 @@
package io.metersphere.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.TestResourceMapper;
import io.metersphere.base.mapper.TestResourcePoolMapper;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.dto.NodeDTO;
import io.metersphere.dto.TestResourcePoolDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
@ -40,16 +34,14 @@ import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
@Transactional(rollbackFor = Exception.class)
public class TestResourcePoolService {
private final static String nodeControllerUrl = "http://%s:%s/status";
@Resource
private TestResourcePoolMapper testResourcePoolMapper;
@Resource
private TestResourceMapper testResourceMapper;
@Resource
private RestTemplate restTemplateWithTimeOut;
@Resource
private LoadTestMapper loadTestMapper;
@Resource
private NodeResourcePoolService nodeResourcePoolService;
public TestResourcePoolDTO addTestResourcePool(TestResourcePoolDTO testResourcePool) {
checkTestResourcePool(testResourcePool);
@ -168,61 +160,14 @@ public class TestResourcePoolService {
}
private boolean validateTestResourcePool(TestResourcePoolDTO testResourcePool) {
return validateNodes(testResourcePool);
}
private boolean validateNodes(TestResourcePoolDTO testResourcePool) {
if (CollectionUtils.isEmpty(testResourcePool.getResources())) {
MSException.throwException(Translator.get("no_nodes_message"));
}
deleteTestResource(testResourcePool.getId());
List<String> nodeIps = testResourcePool.getResources().stream()
.map(resource -> {
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
return nodeDTO.getIp();
})
.distinct()
.collect(Collectors.toList());
if (nodeIps.size() < testResourcePool.getResources().size()) {
MSException.throwException(Translator.get("duplicate_node_ip"));
}
testResourcePool.setStatus(VALID.name());
boolean isValid = true;
for (TestResource resource : testResourcePool.getResources()) {
NodeDTO nodeDTO = JSON.parseObject(resource.getConfiguration(), NodeDTO.class);
boolean isValidate = validateNode(nodeDTO);
if (!isValidate) {
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
resource.setStatus(ResourceStatusEnum.INVALID.name());
isValid = false;
} else {
resource.setStatus(VALID.name());
if (StringUtils.equalsIgnoreCase(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.name())) {
KubernetesResourcePoolService resourcePoolService = CommonBeanFactory.getBean(KubernetesResourcePoolService.class);
if (resourcePoolService == null) {
return false;
}
resource.setTestResourcePoolId(testResourcePool.getId());
updateTestResource(resource);
return resourcePoolService.validate(testResourcePool);
}
return isValid;
}
private boolean validateNode(NodeDTO node) {
try {
ResponseEntity<String> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class);
return HttpStatus.OK.equals(entity.getStatusCode());
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
return false;
}
}
private void updateTestResource(TestResource testResource) {
testResource.setUpdateTime(System.currentTimeMillis());
testResource.setCreateTime(System.currentTimeMillis());
if (StringUtils.isBlank(testResource.getId())) {
testResource.setId(UUID.randomUUID().toString());
}
// 如果是更新操作插入与原来的ID相同的数据
testResourceMapper.insertSelective(testResource);
return nodeResourcePoolService.validate(testResourcePool);
}
private void deleteTestResource(String testResourcePoolId) {

@ -1 +1 @@
Subproject commit 905ca8af61ce966d26109e9c5c8c0aee3ca1324e
Subproject commit 2ba1351aa0135cdf3e5de740fa2d9c185b60ce9d

View File

@ -12,6 +12,7 @@
<el-table-column prop="type" :label="$t('test_resource_pool.type')">
<template v-slot:default="scope">
<span v-if="scope.row.type === 'NODE'">Node</span>
<span v-if="scope.row.type === 'K8S'" v-xpack>Kubernetes</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('test_resource_pool.enable_disable')">
@ -52,7 +53,7 @@
:destroy-on-close="true"
v-loading="result.loading"
>
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule"
ref="createTestResourcePoolForm">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -64,9 +65,40 @@
<el-select v-model="form.type" :placeholder="$t('test_resource_pool.select_pool_type')"
@change="changeResourceType()">
<el-option key="NODE" value="NODE" label="Node">Node</el-option>
<el-option key="K8S" value="K8S" label="Kubernetes" v-xpack>Kubernetes</el-option>
</el-select>
</el-form-item>
<div v-for="(item,index) in infoList " :key="index">
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
<el-row>
<el-col>
<el-form-item prop="controllerUrl" label="Controller URL">
<el-input v-model="item.controllerUrl" autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="masterUrl" label="Master URL">
<el-input v-model="item.masterUrl" autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="password" label="Token">
<el-input v-model="item.token" type="password" show-password autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="1000000000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="node-line" v-if="form.type === 'NODE'">
<el-row>
<el-col :span="8">
@ -115,7 +147,7 @@
:title="$t('test_resource_pool.update_resource_pool')" :visible.sync="updateVisible" width="70%"
:destroy-on-close="true"
@close="closeFunc">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
<el-form :model="form" label-position="right" label-width="120px" size="small" :rules="rule"
ref="updateTestResourcePoolForm">
<el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/>
@ -127,9 +159,40 @@
<el-select v-model="form.type" :placeholder="$t('test_resource_pool.select_pool_type')"
@change="changeResourceType()">
<el-option key="NODE" value="NODE" label="Node">Node</el-option>
<el-option key="K8S" value="K8S" label="Kubernetes" v-xpack>Kubernetes</el-option>
</el-select>
</el-form-item>
<div v-for="(item,index) in infoList " :key="index">
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
<el-row>
<el-col>
<el-form-item prop="controllerUrl" label="Controller URL">
<el-input v-model="item.controllerUrl" autocomplete="off"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="masterUrl" label="Master URL">
<el-input v-model="item.masterUrl" autocomplete="off"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="password" label="Token">
<el-input v-model="item.token" type="password" show-password autocomplete="off"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="1000000000"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="node-line" v-if="form.type === 'NODE'">
<el-row>
<el-col :span="8">

@ -1 +1 @@
Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a
Subproject commit 29a8fc09602fde5708af06582ac972d98eb69836