Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
W23123 2020-03-20 17:06:39 +08:00
commit 4f6a50a9ec
15 changed files with 230 additions and 125 deletions

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum FileType {
JMX
JMX, CSV
}

View File

@ -50,17 +50,17 @@ public class LoadTestController {
@PostMapping(value = "/save", consumes = {"multipart/form-data"})
public String save(
@RequestPart("request") SaveTestPlanRequest request,
@RequestPart(value = "file") MultipartFile file
@RequestPart(value = "file") List<MultipartFile> files
) {
return loadTestService.save(request, file);
return loadTestService.save(request, files);
}
@PostMapping(value = "/edit", consumes = {"multipart/form-data"})
public String edit(
@RequestPart("request") EditTestPlanRequest request,
@RequestPart(value = "file", required = false) MultipartFile file
@RequestPart(value = "file", required = false) List<MultipartFile> files
) {
return loadTestService.edit(request, file);
return loadTestService.edit(request, files);
}
@GetMapping("/get/{testId}")
@ -89,7 +89,7 @@ public class LoadTestController {
}
@GetMapping("/file/metadata/{testId}")
public FileMetadata getFileMetadata(@PathVariable String testId) {
public List<FileMetadata> getFileMetadata(@PathVariable String testId) {
return fileService.getFileMetadataByTestId(testId);
}

View File

@ -1,4 +1,17 @@
package io.metersphere.controller.request.testplan;
import io.metersphere.base.domain.FileMetadata;
import java.util.List;
public class EditTestPlanRequest extends TestPlanRequest {
private List<FileMetadata> updatedFileList;
public List<FileMetadata> getUpdatedFileList() {
return updatedFileList;
}
public void setUpdatedFileList(List<FileMetadata> updatedFileList) {
this.updatedFileList = updatedFileList;
}
}

View File

@ -11,6 +11,7 @@ public class EngineContext {
private String fileType;
private String content;
private Map<String, Object> properties = new HashMap<>();
private Map<String, String> testData = new HashMap<>();
public String getTestId() {
return testId;
@ -67,4 +68,12 @@ public class EngineContext {
public void setFileType(String fileType) {
this.fileType = fileType;
}
public Map<String, String> getTestData() {
return testData;
}
public void setTestData(Map<String, String> testData) {
this.testData = testData;
}
}

View File

@ -9,13 +9,24 @@ import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.engine.docker.DockerTestEngine;
import io.metersphere.engine.kubernetes.KubernetesTestEngine;
import io.metersphere.i18n.Translator;
import io.metersphere.parse.EngineSourceParser;
import io.metersphere.parse.EngineSourceParserFactory;
import io.metersphere.service.FileService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class EngineFactory {
private static FileService fileService;
public static Engine createEngine(String engineType) {
final EngineType type = EngineType.valueOf(engineType);
@ -28,7 +39,11 @@ public class EngineFactory {
return null;
}
public static EngineContext createContext(LoadTestWithBLOBs loadTest, FileMetadata fileMetadata, FileContent fileContent) throws Exception {
public static EngineContext createContext(LoadTestWithBLOBs loadTest, FileMetadata fileMetadata, List<FileMetadata> csvFiles) throws Exception {
final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
if (fileContent == null) {
MSException.throwException(Translator.get("run_load_test_file_content_not_found") + loadTest.getId());
}
final EngineContext engineContext = new EngineContext();
engineContext.setTestId(loadTest.getId());
engineContext.setTestName(loadTest.getName());
@ -48,12 +63,27 @@ public class EngineFactory {
final EngineSourceParser engineSourceParser = EngineSourceParserFactory.createEngineSourceParser(engineContext.getFileType());
if (engineSourceParser == null) {
MSException.throwException("未知的文件类型!");
MSException.throwException("File type unknown");
}
String content = engineSourceParser.parse(engineContext, new ByteArrayInputStream(fileContent.getFile()));
engineContext.setContent(content);
if (CollectionUtils.isNotEmpty(csvFiles)) {
Map<String, String> data = new HashMap<>();
csvFiles.forEach(cf -> {
FileContent csvContent = fileService.getFileContent(cf.getId());
data.put(cf.getName(), new String(csvContent.getFile()));
});
engineContext.setTestData(data);
}
return engineContext;
}
@Resource
private void setFileService(FileService fileService) {
EngineFactory.fileService = fileService;
}
}

View File

@ -11,6 +11,7 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter;
import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec;
import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import org.apache.commons.collections.MapUtils;
import java.util.HashMap;
@ -48,6 +49,9 @@ public class KubernetesTestEngine implements Engine {
}});
item.setData(new HashMap<String, String>() {{
put(context.getTestId() + ".jmx", context.getContent());
if (MapUtils.isNotEmpty(context.getTestData())) {
putAll(context.getTestData());
}
}});
client.configMaps().inNamespace(context.getNamespace()).create(item);
}

View File

@ -29,7 +29,7 @@ public class FileService {
return fileContent.getFile();
}
public FileMetadata getFileMetadataByTestId(String testId) {
public List<FileMetadata> getFileMetadataByTestId(String testId) {
LoadTestFileExample loadTestFileExample = new LoadTestFileExample();
loadTestFileExample.createCriteria().andTestIdEqualTo(testId);
final List<LoadTestFile> loadTestFiles = loadTestFileMapper.selectByExample(loadTestFileExample);
@ -37,8 +37,10 @@ public class FileService {
if (CollectionUtils.isEmpty(loadTestFiles)) {
return null;
}
return fileMetadataMapper.selectByPrimaryKey(loadTestFiles.get(0).getFileId());
List<String> fileIds = loadTestFiles.stream().map(LoadTestFile::getFileId).collect(Collectors.toList());
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdIn(fileIds);
return fileMetadataMapper.selectByExample(example);
}
public FileMetadata getFucFileMetadataByTestId(String testId) {
@ -74,4 +76,17 @@ public class FileService {
fileContentMapper.deleteByExample(fileContentExample);
}
}
public void deleteFileByIds(List<String> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andIdIn(ids);
fileMetadataMapper.deleteByExample(example);
FileContentExample example2 = new FileContentExample();
example2.createCriteria().andFileIdIn(ids);
fileContentMapper.deleteByExample(example2);
}
}

View File

@ -138,39 +138,39 @@ public class FuctionalTestService {
public void run(RunTestPlanRequest request) {
final FucTestWithBLOBs fucTest = fucTestMapper.selectByPrimaryKey(request.getId());
if (fucTest == null) {
MSException.throwException("无法运行测试,未找到测试:" + request.getId());
}
final FileMetadata fileMetadata = fileService.getFileMetadataByTestId(request.getId());
if (fileMetadata == null) {
MSException.throwException("无法运行测试无法获取测试文件元信息测试ID" + request.getId());
}
final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
if (fileContent == null) {
MSException.throwException("无法运行测试无法获取测试文件内容测试ID" + request.getId());
}
System.out.println("开始运行:" + fucTest.getName());
final Engine engine = EngineFactory.createEngine(fileMetadata.getType());
if (engine == null) {
MSException.throwException(String.format("无法运行测试未识别测试文件类型测试ID%s文件类型%s",
request.getId(),
fileMetadata.getType()));
}
boolean init = true;
try {
// init = engine.init(EngineFactory.createContext(fucTest, fileMetadata, fileContent));
} catch (Exception e) {
MSException.throwException(e);
}
if (!init) {
MSException.throwException(String.format("无法运行测试初始化运行环境失败测试ID%s", request.getId()));
}
// engine.start();
// if (fucTest == null) {
// MSException.throwException("无法运行测试,未找到测试:" + request.getId());
// }
//
// final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(request.getId());
// if (fileMetadataList == null) {
// MSException.throwException("无法运行测试无法获取测试文件元信息测试ID" + request.getId());
// }
//
// final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
// if (fileContent == null) {
// MSException.throwException("无法运行测试无法获取测试文件内容测试ID" + request.getId());
// }
//
// System.out.println("开始运行:" + fucTest.getName());
// final Engine engine = EngineFactory.createEngine(fileMetadata.getType());
// if (engine == null) {
// MSException.throwException(String.format("无法运行测试未识别测试文件类型测试ID%s文件类型%s",
// request.getId(),
// fileMetadata.getType()));
// }
//
// boolean init = true;
// try {
//// init = engine.init(EngineFactory.createContext(fucTest, fileMetadata, fileContent));
// } catch (Exception e) {
// MSException.throwException(e);
// }
// if (!init) {
// MSException.throwException(String.format("无法运行测试初始化运行环境失败测试ID%s", request.getId()));
// }
//
//// engine.start();
/// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
}

View File

@ -7,6 +7,7 @@ import io.metersphere.base.mapper.LoadTestFileMapper;
import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
@ -15,6 +16,8 @@ import io.metersphere.dto.LoadTestDTO;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineFactory;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -25,6 +28,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -52,20 +56,18 @@ public class LoadTestService {
fileService.deleteFileByTestId(request.getId());
}
public String save(SaveTestPlanRequest request, MultipartFile file) {
if (file == null) {
public String save(SaveTestPlanRequest request, List<MultipartFile> files) {
if (files == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
final FileMetadata fileMetadata = saveFile(file);
final LoadTestWithBLOBs loadTest = saveLoadTest(request);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(loadTest.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
files.forEach(file -> {
final FileMetadata fileMetadata = saveFile(file);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(loadTest.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
});
return loadTest.getId();
}
@ -98,7 +100,8 @@ public class LoadTestService {
fileMetadata.setSize(file.getSize());
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("JMX");
FileType fileType = getFileType(fileMetadata.getName());
fileMetadata.setType(fileType.name());
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata);
@ -115,15 +118,30 @@ public class LoadTestService {
return fileMetadata;
}
public String edit(EditTestPlanRequest request, MultipartFile file) {
private FileType getFileType(String filename) {
int s = filename.lastIndexOf(".") + 1;
String type = filename.substring(s);
return FileType.valueOf(type.toUpperCase());
}
public String edit(EditTestPlanRequest request, List<MultipartFile> files) {
// 新选择了一个文件删除原来的文件
if (file != null) {
fileService.deleteFileByTestId(request.getId());
final FileMetadata fileMetadata = saveFile(file);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(request.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
List<FileMetadata> updatedFiles = request.getUpdatedFileList();
List<FileMetadata> originFiles = fileService.getFileMetadataByTestId(request.getId());
List<String> updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
List<String> originFileIds = originFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
// 相减
List<String> deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds);
fileService.deleteFileByIds(deleteFileIds);
if (files != null) {
files.forEach(file -> {
final FileMetadata fileMetadata = saveFile(file);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(request.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
});
}
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
@ -149,29 +167,28 @@ public class LoadTestService {
MSException.throwException(Translator.get("run_load_test_not_found") + request.getId());
}
final FileMetadata fileMetadata = fileService.getFileMetadataByTestId(request.getId());
if (fileMetadata == null) {
final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(request.getId());
if (CollectionUtils.isEmpty(fileMetadataList)) {
MSException.throwException(Translator.get("run_load_test_file_not_found") + request.getId());
}
FileMetadata fileMetadata = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name()))
.findFirst().orElseGet(() -> {
throw new RuntimeException(Translator.get("run_load_test_file_not_found") + request.getId());
});
final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
if (fileContent == null) {
MSException.throwException(Translator.get("run_load_test_file_content_not_found") + request.getId());
}
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
LogUtil.info("Load test started " + loadTest.getName());
// engine type (DOCKER|KUBERNETES)
// todo set type
final Engine engine = EngineFactory.createEngine(fileMetadata.getEngine());
if (engine == null) {
MSException.throwException(String.format("Test cannot be runtest ID%sfile type%s",
request.getId(),
fileMetadata.getType()));
MSException.throwException(String.format("Test cannot be runtest ID%sfile type%s", request.getId(), fileMetadata.getType()));
}
boolean init = true;
try {
init = engine.init(EngineFactory.createContext(loadTest, fileMetadata, fileContent));
init = engine.init(EngineFactory.createContext(loadTest, fileMetadata, csvFiles));
} catch (Exception e) {
MSException.throwException(e);
}

View File

@ -84,8 +84,8 @@
let testId = to.path.split('/')[4]; // find testId
if (testId) {
this.$get('/testplan/get/' + testId, response => {
if(response.data){
this.result = this.$get('/testplan/get/' + testId, response => {
if (response.data) {
this.testPlan = response.data;
}
});
@ -96,7 +96,7 @@
created() {
let testId = this.$route.path.split('/')[4];
if (testId) {
this.$get('/testplan/get/' + testId, response => {
this.result = this.$get('/testplan/get/' + testId, response => {
this.testPlan = response.data;
});
}
@ -151,9 +151,13 @@
let formData = new FormData();
let url = this.testPlan.id ? this.editPath : this.savePath;
if (!this.testPlan.file.id) {
formData.append("file", this.testPlan.file);
if (this.$refs.basicConfig.uploadList.length > 0) {
this.$refs.basicConfig.uploadList.forEach(f => {
formData.append("file", f);
});
}
//
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
//
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
//
@ -199,12 +203,7 @@
return false;
}
if (!this.testPlan.file) {
this.$message({
message: this.$t('load_test.jmx_is_null'),
type: 'error'
});
if (!this.$refs.basicConfig.validConfig()) {
return false;
}

View File

@ -190,7 +190,8 @@
<div>{{$t('load_test.custom_http_code')}}</div>
</el-form-item>
<el-form-item>
<el-input size="mini" v-model="statusCodeStr" :placeholder="$t('load_test.separated_by_commas')" @input="checkStatusCode"></el-input>
<el-input size="mini" v-model="statusCodeStr" :placeholder="$t('load_test.separated_by_commas')"
@input="checkStatusCode"></el-input>
</el-form-item>
</el-form>
</el-col>
@ -218,7 +219,7 @@
},
watch: {
'$route'(to, from) {
if(from.name != 'createPerTest' || from.name != 'editPerTest'){
if (from.name !== 'createPerTest' && from.name !== 'editPerTest') {
return;
}
let testId = to.path.split('/')[4];

View File

@ -1,10 +1,11 @@
<template>
<div v-loading="result.loading">
<el-upload
accept=".jmx"
accept=".jmx,.csv"
drag
action=""
:limit="1"
:limit="5"
multiple
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="handleUpload"
@ -32,13 +33,9 @@
:label="$t('load_test.last_modify_time')">
<template slot-scope="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.lastModified | timestampFormatDate }}</span>
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
:label="$t('load_test.file_status')">
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template slot-scope="scope">
@ -66,6 +63,7 @@
jmxDeletePath: '/testplan/file/delete',
fileList: [],
tableData: [],
uploadList: [],
};
},
created() {
@ -82,29 +80,20 @@
},
methods: {
getFileMetadata(testPlan) {
this.fileList = [];//
this.tableData = [];//
this.fileList = [];
this.tableData = [];
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
let file = response.data;
let files = response.data;
if (!file) {
if (!files) {
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
return;
}
this.testPlan.file = file;
this.fileList.push({
id: file.id,
name: file.name
});
this.tableData.push({
id: file.id,
name: file.name,
size: file.size + 'Byte', /// todo: ByteKBMB
type: 'JMX',
lastModified: file.updateTime,
status: 'todo',
// deep copy
this.fileList = JSON.parse(JSON.stringify(files));
this.tableData = JSON.parse(JSON.stringify(files));
this.tableData.map(f => {
f.size = f.size + ' Bytes';
});
})
},
@ -114,18 +103,24 @@
return false;
}
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$message.error(this.$t('load_test.delete_file'));
return false;
}
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
this.tableData.push({
name: file.name,
size: file.size + 'Byte', /// todo: ByteKBMB
type: 'JMX',
lastModified: file.lastModified,
status: 'todo',
size: file.size + ' Bytes', /// todo: ByteKBMB
type: type.toUpperCase(),
updateTime: file.lastModified,
});
return true;
},
handleUpload(uploadResources) {
this.testPlan.file = uploadResources.file;
this.uploadList.push(uploadResources.file);
},
handleDownload(file) {
let data = {
@ -158,7 +153,7 @@
});
},
handleDelete(file, index) {
this.$alert(this.$t('commons.delete_file_confirm') + file.name + "", '', {
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
@ -170,7 +165,9 @@
_handleDelete(file, index) {
this.fileList.splice(index, 1);
this.tableData.splice(index, 1);
this.testPlan.file = null;
//
let i = this.uploadList.indexOf(file);
this.uploadList.splice(i, 1);
},
handleExceed() {
this.$message.error(this.$t('load_test.delete_file'));
@ -179,6 +176,26 @@
/// todo:
return file.size > 0;
},
updatedFileList() {
return this.fileList;//
},
validConfig() {
let newJmxNum = 0, oldJmxNum = 0;
if (this.uploadList.length > 0) {
newJmxNum = this.uploadList.filter(f => f.name.endsWith(".jmx")).length;
}
if (this.fileList.length > 0) {
oldJmxNum = this.fileList.filter(f => f.name.endsWith(".jmx")).length;
}
if (newJmxNum + oldJmxNum !== 1) {
this.$message({
message: this.$t('load_test.jmx_is_null'),
type: 'error'
});
return false;
}
return true;
}
},
}
</script>

View File

@ -113,7 +113,7 @@
},
watch: {
'$route'(to, from) {
if(from.name != 'createPerTest' || from.name != 'editPerTest'){
if (from.name !== 'createPerTest' && from.name !== 'editPerTest') {
return;
}
let testId = to.path.split('/')[4];
@ -126,7 +126,7 @@
},
methods: {
getLoadConfig(testId) {
if(testId) {
if (testId) {
this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data && response.data != "") {

View File

@ -122,14 +122,14 @@ export default {
'is_running': 'Test is running! ',
'test_name_is_null': 'Test name cannot be empty! ',
'project_is_null': 'Project cannot be empty! ',
'jmx_is_null': 'JMX file cannot be empty! ',
'jmx_is_null': 'Can only contain one JMX file',
'file_name': 'File name',
'file_size': 'File size',
'file_type': 'File Type',
'file_status': 'File Status',
'last_modify_time': 'Modify time',
'upload_tips': 'Drag files here, or <em> click to upload </em>',
'upload_type': 'Only JMX files can be uploaded',
'upload_type': 'Only JMX/CSV files can be uploaded',
'related_file_not_found': "No related test file found!",
'delete_file_confirm': 'Confirm delete file:',
'delete_file': "Please delete an existing file first!",

View File

@ -122,14 +122,14 @@ export default {
'is_running': '正在运行!',
'test_name_is_null': '测试名称不能为空!',
'project_is_null': '项目不能为空!',
'jmx_is_null': 'JMX文件不能为空',
'jmx_is_null': '只能包含一个JMX文件',
'file_name': '文件名',
'file_size': '文件大小',
'file_type': '文件类型',
'file_status': '文件状态',
'last_modify_time': '修改时间',
'upload_tips': '将文件拖到此处,或<em>点击上传</em>',
'upload_type': '只能上传JMX文件',
'upload_type': '只能上传JMX/CSV文件',
'related_file_not_found': "未找到关联的测试文件!",
'delete_file_confirm': '确认删除文件: ',
'delete_file': "请先删除已存在的文件!",