|
- package com.educoder.bridge.controller;
-
- import com.alibaba.fastjson.JSONObject;
- import com.educoder.bridge.exception.GameException;
- import com.educoder.bridge.service.*;
- import com.educoder.bridge.settings.AppConfig;
- import com.educoder.bridge.utils.*;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import io.swagger.annotations.ApiParam;
- import org.apache.commons.lang.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.MediaType;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.io.File;
- import java.text.SimpleDateFormat;
-
- /**
- * 和实训相关的接口
- *
- * @author weishao
- * @date 2017/11/02
- */
- @Api(value = "game控制器", hidden = true)
- @RestController
- @RequestMapping("/game")
- public class GameController extends BaseController {
- @Autowired
- private AppConfig appConfig;
- @Autowired
- private GameService gameService;
- @Autowired
- private KubernetesService kubernetesService;
- @Autowired
- private K8sService k8sService;
- @Autowired
- private ThreadPoolTaskExecutor threadPoolTaskExecutor;
-
- private final static Logger logger = LoggerFactory.getLogger(GameController.class);
-
- /**
- * 开启实训:
- * 克隆版本库
- *
- * @return
- */
- @RequestMapping(path = "/openGameInstance")
- @ApiOperation(value = "开启实训", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- public JSONObject openGameInstance(
- @ApiParam(name = "tpmGitURL", required = true, value = "Tpm的gitUrl,需要base64编码") @RequestParam String tpmGitURL,
- @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
- @ApiParam(name = "tpiRepoName", required = true, value = "tpiRepoName") @RequestParam String tpiRepoName
- ) throws Exception {
- logger.info("tpmGitURL: {}\n, tpiID: {}, tpiRepoName: {}", tpmGitURL, tpiID, tpiRepoName);
-
- JSONObject response = new JSONObject();
-
- // 设定工作路径为${workspace}/myshixun_${tpiID}
- String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
-
- // 对当前TPI,从TPM clone 版本库
- tpmGitURL = Base64Util.decode(tpmGitURL);
- gameService.gitClone(path, tpmGitURL, "remote_origin", tpiRepoName);
-
- response.put("code", 0);
- response.put("msg", "开启成功");
- return response;
- }
-
- /**
- * 评测:
- * 对每一次评测请求,先判断是否能立即执行还是需要先排队,如果能立即执行,开启一个线程执行以下步骤
- * 如果需要排队,将相关参数封装成一个线程,存入redis队列,等待执行,并返回标志位给前台,通知前台进行轮询
- *
- * 1. gitpull
- * 2. 如果有端口映射
- * 3. 创建pod 和 service 并 创建定时删除pod的定时任务
- * 4. 在pod中执行评测脚本
- * 5. 回传结果
- *
- *
- * @param tpiID
- * @param tpiGitURL
- * @param buildID
- * @param instanceChallenge
- * @param resubmit
- * @param times
- * @return
- * @throws Exception
- */
- @RequestMapping(path = "/gameEvaluate")
- @ApiOperation(value = "评测", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- public JSONObject gameEvaluate(
- @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
- @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,需要base64编码") @RequestParam String tpiGitURL,
- @ApiParam(name = "buildID", required = true, value = "本次评测ID") @RequestParam String buildID,
- @ApiParam(name = "instanceChallenge", required = true, value = "当前处在第几关") @RequestParam String instanceChallenge,
- @ApiParam(name = "testCases", required = true, value = "测试用例") @RequestParam String testCases,
- @ApiParam(name = "tpmScript", required = true, value = "tpm评测脚本,需要base64编码") @RequestParam String tpmScript,
- @ApiParam(name = "timeLimit", required = false, value = "时间限制") Integer timeLimit,
- @ApiParam(name = "resubmit", required = true, value = "是否是重复评测") @RequestParam String resubmit,
- @ApiParam(name = "times", required = true, value = "是否是时间轮询请求") @RequestParam Integer times,
- @ApiParam(name = "needPortMapping", required = false, value = "容器中需要被映射的端口") Integer needPortMapping,
- @ApiParam(name = "podType", required = true, value = "pod类型(0.evaluate,1.webssh,2.evassh)") @RequestParam Integer podType,
- @ApiParam(name = "file", required = false, value = "需要传文件的实训,给出文件存放路径(一个目录)及文件类型") String file,
- @ApiParam(name = "containers", required = true, value = "需要使用的容器,base64编码") @RequestParam String containers)
- throws Exception {
- // 记录开始时间
- SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
- logger.info("training_task_status start#1**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
-
- long evaStart = System.currentTimeMillis();
- String evaluateStart = dateformat.format(evaStart);
- JSONObject cost = new JSONObject();
- cost.put("evaluateStart", evaluateStart);
- cost.put("evaluateAllTime", evaStart);
- ConstantUtil.costMap.put(tpiID, cost);
-
- JSONObject response = new JSONObject();
-
- // 所有构建需要的参数放到map中,供执行线程使用
- tpiGitURL = Base64Util.decode(tpiGitURL);
- needPortMapping = needPortMapping == null ? 0 : needPortMapping;
- timeLimit = timeLimit == null ? Integer.parseInt(appConfig.getDefaultTimeLimit()) : timeLimit;
- testCases = Base64Util.decode(testCases);
- containers = Base64Util.decode(containers);
-
- // 每次评测均生成新TPI评测脚本
- tpmScript = Base64Util.decode(tpmScript);
- String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
- String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
- logger.info("generateTpiEvaluateShellScript start#2**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
- gameService.generateTpiEvaluateShellScript(tpmScript, path, tpiRepoName);
-
- logger.info("retryformat start#3**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
- JSONObject buildParams = new JSONObject(true);
- buildParams.put("tpiID", tpiID);
- buildParams.put("tpiGitURL", tpiGitURL);
- buildParams.put("buildID", buildID);
- buildParams.put("instanceChallenge", instanceChallenge);
- buildParams.put("testCases", testCases);
- buildParams.put("timeLimit", timeLimit);
- buildParams.put("resubmit", resubmit);
- buildParams.put("needPortMapping", needPortMapping);
- buildParams.put("podType", podType);
- buildParams.put("containers", containers);
-
- // 若实训生成文件
- if (!StringUtils.isEmpty(file)) {
- file = Base64Util.decode(file);
- buildParams.put("file", file);
- JSONObject jsonObject = JSONObject.parseObject(file);
- // 清空目标文件夹以防止影响此次评测结果
- String dir = path + "/" + tpiRepoName + "/" + jsonObject.getString("path");
- StringHelper.deleteFiles(dir, "pic");
- StringHelper.deleteFiles(dir, "apk");
- StringHelper.deleteFiles(dir, "html");
- }
-
- // 最大running pod数量
- int maxRunningPodNum = Integer.parseInt(appConfig.getMaxRunningPodNum());
-
- // podName
- String podName = podType == 0 ? "evaluate-" + tpiID : "evassh-" + tpiID;
-
- // 若需要端口映射服务,则分配端口,将podName-port键值对存储于redis
- String port = "-1";
- if(needPortMapping != -1) {
- port = JedisUtil.hget("port", podName);
- if(port == null) {
- port = PortUtil.getPort() + "";
- JedisUtil.hset("port", podName, port);
- }
- }
-
- // 构建任务字符串,便于redis存储
- String task = buildParams.toJSONString();
-
- // 此次请求只为轮询时间,只返回轮询的时间结果
- if (times != 1) {
- double buildRank;
-
- try {
- buildRank = JedisUtil.zrank("task", task);
- } catch (Exception e) {
- response.put("ableToCreate", 0);
- response.put("waitNum", maxRunningPodNum + JedisUtil.zlen("task"));
- response.put("code", 0);
- response.put("msg", "等待评测");
- return response;
- }
-
- if (buildRank != Double.MIN_VALUE) {
- logger.debug("任务:{} : 还在等待队列中!", task);
-
- response.put("ableToCreate", 0);
- response.put("waitNum", buildRank == Double.MAX_VALUE ? maxRunningPodNum + JedisUtil.zlen("task") : buildRank + maxRunningPodNum);
- response.put("code", 0);
- response.put("msg", "等待评测");
- return response;
- } else {
- // 轮询时间请求,发现目标任务不在队列,就直接认为其正在运行
- logger.debug("任务:{} : 正在执行中!", task);
-
- response.put("ableToCreate", 1);
- response.put("waitNum", 0);
- response.put("code", 0);
- response.put("port", Integer.parseInt(port));
- response.put("msg", "正在评测");
- return response;
- }
- }
-
-
- logger.info("kubernetes start#4**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
- // 获取Running状态的pod数
- int runningPodNum = kubernetesService.getRunningPodNum();
- // 判断是否可以立即执行(若pod已经存在,或是,pod不存在但是此时可以创建pod)
- boolean executeImmediately = kubernetesService.isPodRunning("tpiID", tpiID)
- || (runningPodNum < maxRunningPodNum && k8sService.ableToEvaluate());
-
- if (executeImmediately) {
- // 直接执行任务
- logger.info("execute start#5**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
- BuildThread buildThread = gameService.getBuildThread(buildParams);
- threadPoolTaskExecutor.execute(buildThread);
- long evaEnd = System.currentTimeMillis();
- long costTime = evaEnd - evaStart;
-
- response.put("ableToCreate", 1);
- response.put("costTime", costTime);
- response.put("waitNum", 0);
- response.put("code", 0);
- response.put("port", port);
- response.put("msg", "评测完成");
-
- logger.debug("直接执行任务!task: {}, tpi: {}", task, tpiID);
- } else {
- // 否则,将构建任务推入redis
- JedisUtil.zpush("task", task);
-
- //评测任务等待执行
- response.put("ableToCreate", 0);
- //在等待队列中的位置
- response.put("waitNum", JedisUtil.zlen("task") + maxRunningPodNum);
- response.put("code", 0);
- response.put("msg", "等待评测");
-
- logger.debug("任务入队等待!task: {}, tpi: {}", task, tpiID);
- }
-
- return response;
- }
-
-
- /**
- * tpm版本库已更新,同步
- *
- * @param tpiID
- * @param tpiGitURL
- * @param tpmGitURL
- * @return
- */
- @RequestMapping(path = "/resetTpmRepository", method = RequestMethod.POST)
- @ApiOperation(value = "tpm版本库已更新,同步操作", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- public JSONObject reset(
- @ApiParam(name = "tpiID", required = true, value = "tpi") @RequestParam String tpiID,
- @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,base64编码") @RequestParam String tpiGitURL,
- @ApiParam(name = "tpmGitURL", required = true, value = "学员对应当前实训的tpm版本库地址,base64编码") @RequestParam String tpmGitURL,
- @ApiParam(name = "identifier", required = true, value = "push权限") @RequestParam String identifier)
- throws Exception {
-
- logger.debug("tpm版本库已更新,同步tpi版本库,tpiID:{}", tpiID);
-
- JSONObject response = new JSONObject();
-
- tpmGitURL = Base64Util.decode(tpmGitURL);
- tpiGitURL = Base64Util.decode(tpiGitURL);
- String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
- String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
-
- try {
- // 从tpm远程库拉取代码到myshixun版本库然后强推到tpi远程库
- gameService.gitPullFromTpm(path, tpmGitURL, tpiRepoName);
- gameService.gitPushToTpi(path, tpiGitURL, identifier);
-
- logger.debug("tpm库更新内容已同步,tpiID:{}", tpiID);
- response.put("code", 0);
- response.put("msg", "版本库更新成功");
- } catch (GameException e) {
- logger.debug("tpm库更新内容同步失败, tpiID:{}", tpiID);
- response.put("code", -1);
- response.put("msg", "版本库同步失败!");
- }
-
- return response;
- }
-
- /**
- * tpi版本库head是否存在,若缺失则修复
- *
- * @param tpiID
- * @param tpiGitURL
- * @return
- * @throws Exception
- */
- @RequestMapping(path = "/check")
- @ApiOperation(value = "进入实训时做校验", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- public JSONObject check(
- @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
- @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,base64编码") @RequestParam String tpiGitURL)
- throws Exception {
-
- JSONObject response = new JSONObject();
-
- tpiGitURL = Base64Util.decode(tpiGitURL);
- String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
- String tpiRepoPath = appConfig.getWorkspace() + "/myshixun_" + tpiID + "/" + tpiRepoName;
-
- // 远程tpi库是否缺失head了
- boolean noHead = ShellUtil.execute("cd " + tpiRepoPath + " && git remote show origin").contains("unknown");
- if (noHead) {
- logger.debug("tpiID:{} 远程tpi版本库head丢失", tpiID);
-
- String identifier = StringHelper.getIdentifier(tpiGitURL);
-
- // 处理仓库head丢失 ssh -p1122 git@10.9.191.219 'cd /home/git/repositories/p79061248/klp26sqc.git/refs/tmp/;
- // cd `ls -t | sed -n "2p"`; cat head >../../heads/master; git update-ref HEAD `cat head`'
- String command = "ssh -p1122 git@" + appConfig.getGitIP() + " 'cd repositories/" + identifier + "/"
- + tpiRepoName + ".git/refs/tmp; cd `ls -t | sed -n \"2p\"`; cat head >../../heads/master; git update-ref HEAD `cat head`'";
- JSONObject result = ShellUtil.executeAndGetExitStatus(command);
-
- response.put("fixHead", result.getIntValue("exitStatus"));
- }
-
- response.put("msg", "finished");
- return response;
- }
-
- }
|