You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

GameController.java 16 kB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package com.educoder.bridge.controller;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.educoder.bridge.exception.GameException;
  4. import com.educoder.bridge.service.*;
  5. import com.educoder.bridge.settings.AppConfig;
  6. import com.educoder.bridge.utils.*;
  7. import io.swagger.annotations.Api;
  8. import io.swagger.annotations.ApiOperation;
  9. import io.swagger.annotations.ApiParam;
  10. import org.apache.commons.lang.StringUtils;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.http.MediaType;
  15. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  16. import org.springframework.web.bind.annotation.RequestMapping;
  17. import org.springframework.web.bind.annotation.RequestMethod;
  18. import org.springframework.web.bind.annotation.RequestParam;
  19. import org.springframework.web.bind.annotation.RestController;
  20. import java.io.File;
  21. import java.text.SimpleDateFormat;
  22. /**
  23. * 和实训相关的接口
  24. *
  25. * @author weishao
  26. * @date 2017/11/02
  27. */
  28. @Api(value = "game控制器", hidden = true)
  29. @RestController
  30. @RequestMapping("/game")
  31. public class GameController extends BaseController {
  32. @Autowired
  33. private AppConfig appConfig;
  34. @Autowired
  35. private GameService gameService;
  36. @Autowired
  37. private KubernetesService kubernetesService;
  38. @Autowired
  39. private K8sService k8sService;
  40. @Autowired
  41. private ThreadPoolTaskExecutor threadPoolTaskExecutor;
  42. private final static Logger logger = LoggerFactory.getLogger(GameController.class);
  43. /**
  44. * 开启实训:
  45. * 克隆版本库
  46. *
  47. * @return
  48. */
  49. @RequestMapping(path = "/openGameInstance")
  50. @ApiOperation(value = "开启实训", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  51. public JSONObject openGameInstance(
  52. @ApiParam(name = "tpmGitURL", required = true, value = "Tpm的gitUrl,需要base64编码") @RequestParam String tpmGitURL,
  53. @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
  54. @ApiParam(name = "tpiRepoName", required = true, value = "tpiRepoName") @RequestParam String tpiRepoName
  55. ) throws Exception {
  56. logger.info("tpmGitURL: {}\n, tpiID: {}, tpiRepoName: {}", tpmGitURL, tpiID, tpiRepoName);
  57. JSONObject response = new JSONObject();
  58. // 设定工作路径为${workspace}/myshixun_${tpiID}
  59. String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
  60. // 对当前TPI,从TPM clone 版本库
  61. tpmGitURL = Base64Util.decode(tpmGitURL);
  62. gameService.gitClone(path, tpmGitURL, "remote_origin", tpiRepoName);
  63. response.put("code", 0);
  64. response.put("msg", "开启成功");
  65. return response;
  66. }
  67. /**
  68. * 评测:
  69. * 对每一次评测请求,先判断是否能立即执行还是需要先排队,如果能立即执行,开启一个线程执行以下步骤
  70. * 如果需要排队,将相关参数封装成一个线程,存入redis队列,等待执行,并返回标志位给前台,通知前台进行轮询
  71. *
  72. * 1. gitpull
  73. * 2. 如果有端口映射
  74. * 3. 创建pod 和 service 并 创建定时删除pod的定时任务
  75. * 4. 在pod中执行评测脚本
  76. * 5. 回传结果
  77. *
  78. *
  79. * @param tpiID
  80. * @param tpiGitURL
  81. * @param buildID
  82. * @param instanceChallenge
  83. * @param resubmit
  84. * @param times
  85. * @return
  86. * @throws Exception
  87. */
  88. @RequestMapping(path = "/gameEvaluate")
  89. @ApiOperation(value = "评测", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  90. public JSONObject gameEvaluate(
  91. @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
  92. @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,需要base64编码") @RequestParam String tpiGitURL,
  93. @ApiParam(name = "buildID", required = true, value = "本次评测ID") @RequestParam String buildID,
  94. @ApiParam(name = "instanceChallenge", required = true, value = "当前处在第几关") @RequestParam String instanceChallenge,
  95. @ApiParam(name = "testCases", required = true, value = "测试用例") @RequestParam String testCases,
  96. @ApiParam(name = "tpmScript", required = true, value = "tpm评测脚本,需要base64编码") @RequestParam String tpmScript,
  97. @ApiParam(name = "timeLimit", required = false, value = "时间限制") Integer timeLimit,
  98. @ApiParam(name = "resubmit", required = true, value = "是否是重复评测") @RequestParam String resubmit,
  99. @ApiParam(name = "times", required = true, value = "是否是时间轮询请求") @RequestParam Integer times,
  100. @ApiParam(name = "needPortMapping", required = false, value = "容器中需要被映射的端口") Integer needPortMapping,
  101. @ApiParam(name = "podType", required = true, value = "pod类型(0.evaluate,1.webssh,2.evassh)") @RequestParam Integer podType,
  102. @ApiParam(name = "file", required = false, value = "需要传文件的实训,给出文件存放路径(一个目录)及文件类型") String file,
  103. @ApiParam(name = "containers", required = true, value = "需要使用的容器,base64编码") @RequestParam String containers)
  104. throws Exception {
  105. // 记录开始时间
  106. SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
  107. logger.info("training_task_status start#1**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
  108. long evaStart = System.currentTimeMillis();
  109. String evaluateStart = dateformat.format(evaStart);
  110. JSONObject cost = new JSONObject();
  111. cost.put("evaluateStart", evaluateStart);
  112. cost.put("evaluateAllTime", evaStart);
  113. ConstantUtil.costMap.put(tpiID, cost);
  114. JSONObject response = new JSONObject();
  115. // 所有构建需要的参数放到map中,供执行线程使用
  116. tpiGitURL = Base64Util.decode(tpiGitURL);
  117. needPortMapping = needPortMapping == null ? 0 : needPortMapping;
  118. timeLimit = timeLimit == null ? Integer.parseInt(appConfig.getDefaultTimeLimit()) : timeLimit;
  119. testCases = Base64Util.decode(testCases);
  120. containers = Base64Util.decode(containers);
  121. // 每次评测均生成新TPI评测脚本
  122. tpmScript = Base64Util.decode(tpmScript);
  123. String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
  124. String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
  125. logger.info("generateTpiEvaluateShellScript start#2**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
  126. gameService.generateTpiEvaluateShellScript(tpmScript, path, tpiRepoName);
  127. logger.info("retryformat start#3**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
  128. JSONObject buildParams = new JSONObject(true);
  129. buildParams.put("tpiID", tpiID);
  130. buildParams.put("tpiGitURL", tpiGitURL);
  131. buildParams.put("buildID", buildID);
  132. buildParams.put("instanceChallenge", instanceChallenge);
  133. buildParams.put("testCases", testCases);
  134. buildParams.put("timeLimit", timeLimit);
  135. buildParams.put("resubmit", resubmit);
  136. buildParams.put("needPortMapping", needPortMapping);
  137. buildParams.put("podType", podType);
  138. buildParams.put("containers", containers);
  139. // 若实训生成文件
  140. if (!StringUtils.isEmpty(file)) {
  141. file = Base64Util.decode(file);
  142. buildParams.put("file", file);
  143. JSONObject jsonObject = JSONObject.parseObject(file);
  144. // 清空目标文件夹以防止影响此次评测结果
  145. String dir = path + "/" + tpiRepoName + "/" + jsonObject.getString("path");
  146. StringHelper.deleteFiles(dir, "pic");
  147. StringHelper.deleteFiles(dir, "apk");
  148. StringHelper.deleteFiles(dir, "html");
  149. }
  150. // 最大running pod数量
  151. int maxRunningPodNum = Integer.parseInt(appConfig.getMaxRunningPodNum());
  152. // podName
  153. String podName = podType == 0 ? "evaluate-" + tpiID : "evassh-" + tpiID;
  154. // 若需要端口映射服务,则分配端口,将podName-port键值对存储于redis
  155. String port = "-1";
  156. if(needPortMapping != -1) {
  157. port = JedisUtil.hget("port", podName);
  158. if(port == null) {
  159. port = PortUtil.getPort() + "";
  160. JedisUtil.hset("port", podName, port);
  161. }
  162. }
  163. // 构建任务字符串,便于redis存储
  164. String task = buildParams.toJSONString();
  165. // 此次请求只为轮询时间,只返回轮询的时间结果
  166. if (times != 1) {
  167. double buildRank;
  168. try {
  169. buildRank = JedisUtil.zrank("task", task);
  170. } catch (Exception e) {
  171. response.put("ableToCreate", 0);
  172. response.put("waitNum", maxRunningPodNum + JedisUtil.zlen("task"));
  173. response.put("code", 0);
  174. response.put("msg", "等待评测");
  175. return response;
  176. }
  177. if (buildRank != Double.MIN_VALUE) {
  178. logger.debug("任务:{} : 还在等待队列中!", task);
  179. response.put("ableToCreate", 0);
  180. response.put("waitNum", buildRank == Double.MAX_VALUE ? maxRunningPodNum + JedisUtil.zlen("task") : buildRank + maxRunningPodNum);
  181. response.put("code", 0);
  182. response.put("msg", "等待评测");
  183. return response;
  184. } else {
  185. // 轮询时间请求,发现目标任务不在队列,就直接认为其正在运行
  186. logger.debug("任务:{} : 正在执行中!", task);
  187. response.put("ableToCreate", 1);
  188. response.put("waitNum", 0);
  189. response.put("code", 0);
  190. response.put("port", Integer.parseInt(port));
  191. response.put("msg", "正在评测");
  192. return response;
  193. }
  194. }
  195. logger.info("kubernetes start#4**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
  196. // 获取Running状态的pod数
  197. int runningPodNum = kubernetesService.getRunningPodNum();
  198. // 判断是否可以立即执行(若pod已经存在,或是,pod不存在但是此时可以创建pod)
  199. boolean executeImmediately = kubernetesService.isPodRunning("tpiID", tpiID)
  200. || (runningPodNum < maxRunningPodNum && k8sService.ableToEvaluate());
  201. if (executeImmediately) {
  202. // 直接执行任务
  203. logger.info("execute start#5**{}**** {}", tpiID, dateformat.format(System.currentTimeMillis()));
  204. BuildThread buildThread = gameService.getBuildThread(buildParams);
  205. threadPoolTaskExecutor.execute(buildThread);
  206. long evaEnd = System.currentTimeMillis();
  207. long costTime = evaEnd - evaStart;
  208. response.put("ableToCreate", 1);
  209. response.put("costTime", costTime);
  210. response.put("waitNum", 0);
  211. response.put("code", 0);
  212. response.put("port", port);
  213. response.put("msg", "评测完成");
  214. logger.debug("直接执行任务!task: {}, tpi: {}", task, tpiID);
  215. } else {
  216. // 否则,将构建任务推入redis
  217. JedisUtil.zpush("task", task);
  218. //评测任务等待执行
  219. response.put("ableToCreate", 0);
  220. //在等待队列中的位置
  221. response.put("waitNum", JedisUtil.zlen("task") + maxRunningPodNum);
  222. response.put("code", 0);
  223. response.put("msg", "等待评测");
  224. logger.debug("任务入队等待!task: {}, tpi: {}", task, tpiID);
  225. }
  226. return response;
  227. }
  228. /**
  229. * tpm版本库已更新,同步
  230. *
  231. * @param tpiID
  232. * @param tpiGitURL
  233. * @param tpmGitURL
  234. * @return
  235. */
  236. @RequestMapping(path = "/resetTpmRepository", method = RequestMethod.POST)
  237. @ApiOperation(value = "tpm版本库已更新,同步操作", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  238. public JSONObject reset(
  239. @ApiParam(name = "tpiID", required = true, value = "tpi") @RequestParam String tpiID,
  240. @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,base64编码") @RequestParam String tpiGitURL,
  241. @ApiParam(name = "tpmGitURL", required = true, value = "学员对应当前实训的tpm版本库地址,base64编码") @RequestParam String tpmGitURL,
  242. @ApiParam(name = "identifier", required = true, value = "push权限") @RequestParam String identifier)
  243. throws Exception {
  244. logger.debug("tpm版本库已更新,同步tpi版本库,tpiID:{}", tpiID);
  245. JSONObject response = new JSONObject();
  246. tpmGitURL = Base64Util.decode(tpmGitURL);
  247. tpiGitURL = Base64Util.decode(tpiGitURL);
  248. String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
  249. String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID;
  250. try {
  251. // 从tpm远程库拉取代码到myshixun版本库然后强推到tpi远程库
  252. gameService.gitPullFromTpm(path, tpmGitURL, tpiRepoName);
  253. gameService.gitPushToTpi(path, tpiGitURL, identifier);
  254. logger.debug("tpm库更新内容已同步,tpiID:{}", tpiID);
  255. response.put("code", 0);
  256. response.put("msg", "版本库更新成功");
  257. } catch (GameException e) {
  258. logger.debug("tpm库更新内容同步失败, tpiID:{}", tpiID);
  259. response.put("code", -1);
  260. response.put("msg", "版本库同步失败!");
  261. }
  262. return response;
  263. }
  264. /**
  265. * tpi版本库head是否存在,若缺失则修复
  266. *
  267. * @param tpiID
  268. * @param tpiGitURL
  269. * @return
  270. * @throws Exception
  271. */
  272. @RequestMapping(path = "/check")
  273. @ApiOperation(value = "进入实训时做校验", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  274. public JSONObject check(
  275. @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID,
  276. @ApiParam(name = "tpiGitURL", required = true, value = "学员对应当前实训的版本库地址,base64编码") @RequestParam String tpiGitURL)
  277. throws Exception {
  278. JSONObject response = new JSONObject();
  279. tpiGitURL = Base64Util.decode(tpiGitURL);
  280. String tpiRepoName = StringHelper.getRepoName(tpiGitURL);
  281. String tpiRepoPath = appConfig.getWorkspace() + "/myshixun_" + tpiID + "/" + tpiRepoName;
  282. // 远程tpi库是否缺失head了
  283. boolean noHead = ShellUtil.execute("cd " + tpiRepoPath + " && git remote show origin").contains("unknown");
  284. if (noHead) {
  285. logger.debug("tpiID:{} 远程tpi版本库head丢失", tpiID);
  286. String identifier = StringHelper.getIdentifier(tpiGitURL);
  287. // 处理仓库head丢失 ssh -p1122 git@10.9.191.219 'cd /home/git/repositories/p79061248/klp26sqc.git/refs/tmp/;
  288. // cd `ls -t | sed -n "2p"`; cat head >../../heads/master; git update-ref HEAD `cat head`'
  289. String command = "ssh -p1122 git@" + appConfig.getGitIP() + " 'cd repositories/" + identifier + "/"
  290. + tpiRepoName + ".git/refs/tmp; cd `ls -t | sed -n \"2p\"`; cat head >../../heads/master; git update-ref HEAD `cat head`'";
  291. JSONObject result = ShellUtil.executeAndGetExitStatus(command);
  292. response.put("fixHead", result.getIntValue("exitStatus"));
  293. }
  294. response.put("msg", "finished");
  295. return response;
  296. }
  297. }

软件技术创新是当今国家整体创新能力的重要基础和发展引擎。近年来,大众参与的软件创新与创业活动已经成为网络时代软件开发和应用的新形态,正在快速改变着全球软件创新模式和软件产业格局。 ...