| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
abe0990313 | 云平台控制器的一些优化 | 8 years ago |
| @@ -0,0 +1,25 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import org.springframework.web.bind.annotation.ModelAttribute; | |||||
| import javax.servlet.http.HttpServletRequest; | |||||
| import javax.servlet.http.HttpServletResponse; | |||||
| import javax.servlet.http.HttpSession; | |||||
| /** | |||||
| * @author lqk | |||||
| * @version 0.1 | |||||
| */ | |||||
| public class BaseController { | |||||
| protected HttpServletRequest request; | |||||
| protected HttpServletResponse response; | |||||
| protected HttpSession session; | |||||
| @ModelAttribute | |||||
| public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) { | |||||
| this.request = request; | |||||
| this.response = response; | |||||
| this.session = request.getSession(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,138 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.exception.GameException; | |||||
| import com.educoder.bridge.service.GameService; | |||||
| import com.educoder.bridge.settings.AppConfig; | |||||
| import com.educoder.bridge.utils.Base64Util; | |||||
| import io.swagger.annotations.Api; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import io.swagger.annotations.ApiParam; | |||||
| import org.apache.commons.io.FileUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.http.MediaType; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RequestParam; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.nio.charset.Charset; | |||||
| /** | |||||
| * created by weishao at 2017/8/7 | |||||
| * 数据迁移控制器,用于保证已经发布的实训不受影响 | |||||
| */ | |||||
| @Api(value = "数据迁移控制器", hidden = true) | |||||
| @RestController | |||||
| @RequestMapping("/dataTransfer") | |||||
| public class DataTransferController extends BaseController{ | |||||
| @Autowired | |||||
| private GameService gameService; | |||||
| @Autowired | |||||
| private AppConfig appConfig; | |||||
| private static final Logger logger = LoggerFactory.getLogger(DataTransferController.class); | |||||
| @RequestMapping(path = "/transfer") | |||||
| @ApiOperation(value = "数据迁移,写测试用例,生成模板脚本,tpi评测脚本", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject transfer( | |||||
| @ApiParam(name = "gameInfo", required = true, value = "实训的相关配置,base64编码") @RequestParam String gameInfo, | |||||
| @ApiParam(name = "tpiList", required = true, value = "实训实例的信息,包括tpiID与instanceGitURL") @RequestParam String tpiList) { | |||||
| logger.debug("/dataTransfer/transfer: gameInfo: " + gameInfo + ", tpiList: " + tpiList); | |||||
| JSONObject result = new JSONObject(); | |||||
| // // 将字符串解码后转换为JSON对象 | |||||
| // gameInfo = Base64Util.decode(gameInfo); | |||||
| // JSONObject info = JSONObject.parseObject(gameInfo); | |||||
| // | |||||
| // tpiList = Base64Util.decode(tpiList); | |||||
| // JSONArray list = JSONObject.parseArray(tpiList); | |||||
| // | |||||
| // try { | |||||
| // transferTPM(info); | |||||
| // | |||||
| // for (int i = 0; i < list.size(); i++) { | |||||
| // try { | |||||
| // transferTPI(info.getString("tpmID"), list.getJSONObject(i).getString("tpiID"), | |||||
| // list.getJSONObject(i).getString("instanceGitURL")); | |||||
| // } catch (GameException e) { | |||||
| // logger.error("TPI 转换失败,tpiID:{}", list.getJSONObject(i).getString("tpiID")); | |||||
| // continue; | |||||
| // } | |||||
| // } | |||||
| // }catch (GameException e) { | |||||
| // | |||||
| // logger.error("TPM 转换失败,tpmID:{}", info.getString("tpmID")); | |||||
| // result.put("code", -1); | |||||
| // result.put("msg", "转换失败"); | |||||
| // } | |||||
| result.put("code", 0); | |||||
| result.put("msg", "转换成功"); | |||||
| return result; | |||||
| } | |||||
| @RequestMapping(path = "/transferTPI") | |||||
| @ApiOperation(value = "数据迁移,单个tpi迁移", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject transferATPI( | |||||
| @ApiParam(name = "tpmID", required = true, value = "tpmID") @RequestParam String tpmID, | |||||
| @ApiParam(name = "tpiID", required = true, value = "tpiID") @RequestParam String tpiID, | |||||
| @ApiParam(name = "instanceGitURL", required = true, value = "TPI版本库地址,base64编码") @RequestParam String instanceGitURL) | |||||
| throws GameException { | |||||
| JSONObject result = new JSONObject(); | |||||
| instanceGitURL = Base64Util.decode(instanceGitURL); | |||||
| transferTPI (tpmID, tpiID, instanceGitURL); | |||||
| result.put("code", 0); | |||||
| result.put("msg", "转换成功"); | |||||
| return result; | |||||
| } | |||||
| /** | |||||
| * 转换tpm | |||||
| * @param gameInfo | |||||
| */ | |||||
| private void transferTPM (JSONObject gameInfo) throws GameException { | |||||
| logger.debug("DataTransferController: transferTPM: gameInfo: " + gameInfo); | |||||
| // 将测试用例写入工作目录下的文件中 | |||||
| // gameService.generateTestCasesForGame(gameInfo.getString("tpmID"), | |||||
| // gameInfo.getJSONArray("testCases")); | |||||
| // | |||||
| // // 生成Tpm评测脚本到工作目录下指定文件 | |||||
| // gameService.generateTpmEvaluateShellScript(gameInfo); | |||||
| } | |||||
| /** | |||||
| * 转换tpi | |||||
| * @param tpmID | |||||
| * @param tpiID | |||||
| * @param instanceGitURL | |||||
| */ | |||||
| private void transferTPI (String tpmID, String tpiID, String instanceGitURL) throws GameException { | |||||
| logger.debug("DataTransferController: transferTPI: tpmID: " + tpmID + ", tpiID: " + tpiID + ", instanceGitURL: " + instanceGitURL); | |||||
| // String repoName = StringUtil.getRepoName(instanceGitURL); | |||||
| // String path = appConfig.getWorkspace() + File.separator + "myshixun_" + tpiID; | |||||
| // // 删除旧数据 | |||||
| // if (!StringUtils.isEmpty(tpiID)) { | |||||
| // ShellUtil.execute("rm -rf " + path); | |||||
| // } | |||||
| // // 克隆版本库 | |||||
| // gameService.gitClone(path, instanceGitURL, "origin", repoName); | |||||
| // | |||||
| // //根据实训模板评测脚本生成Tpi脚本到工作目录 | |||||
| // gameService.generateTpiEvaluateShellScript(appConfig.getWorkspace() + "/shell/" + tpmID + "/evaluate.sh", | |||||
| // appConfig.getWorkspace() + "/myshixun_" + tpiID + "/" + repoName + "/"); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,97 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.service.DockerService; | |||||
| import com.educoder.bridge.utils.ClientUtil; | |||||
| import com.spotify.docker.client.DockerClient; | |||||
| import com.spotify.docker.client.exceptions.DockerException; | |||||
| import io.swagger.annotations.Api; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import io.swagger.annotations.ApiParam; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.http.MediaType; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RequestParam; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| @Api(value = "镜像管理", hidden = true) | |||||
| @RestController | |||||
| @RequestMapping("/docker") | |||||
| public class DockerController { | |||||
| @Autowired | |||||
| private DockerService dockerService; | |||||
| private Logger logger = LoggerFactory.getLogger(DockerController.class); | |||||
| @RequestMapping(path = "/images") | |||||
| @ApiOperation(value = "获取全部实训镜像", httpMethod = "GET") | |||||
| public JSONObject getImages() throws DockerException, InterruptedException { | |||||
| logger.info("收到获取镜像请求"); | |||||
| JSONObject result = new JSONObject(); | |||||
| // 获取主节点全部镜像 | |||||
| DockerClient docker = ClientUtil.getDocker(); | |||||
| List<String> imageList = dockerService.getImageList(docker); | |||||
| result.put("images", imageList); | |||||
| // 获取未同步的镜像 | |||||
| List<DockerClient> dockerDeputies = ClientUtil.getDockerDeputies(); | |||||
| List<String> imageDiff = dockerService.imageDiff(imageList, dockerDeputies); | |||||
| result.put("imagesNotSync", imageDiff); | |||||
| result.put("code", 0); | |||||
| return result; | |||||
| } | |||||
| @RequestMapping(path = "/syncImage") | |||||
| @ApiOperation(value = "同步镜像", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject syncImage( | |||||
| @ApiParam(name = "imageName", required = true, value = "镜像名") @RequestParam String imageName) | |||||
| throws DockerException, InterruptedException { | |||||
| logger.info("imageName: {}", imageName); | |||||
| JSONObject result = new JSONObject(); | |||||
| DockerClient dockerMaster = ClientUtil.getDocker(); | |||||
| List<DockerClient> dockerDeputies = ClientUtil.getDockerDeputies(); | |||||
| dockerService.syncImage(dockerMaster, dockerDeputies, imageName); | |||||
| result.put("code", 0); | |||||
| return result; | |||||
| } | |||||
| @RequestMapping(path = "/updateImage") | |||||
| @ApiOperation(value = "镜像有更新", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject updateImage( | |||||
| @ApiParam(name = "imageName", required = true, value = "镜像名") @RequestParam String imageName, | |||||
| @ApiParam(name = "imageID", required = true, value = "旧的镜像ID") @RequestParam String imageID, | |||||
| @ApiParam(name = "flag", required = true, value = "更新行为选择,0更新 1回退") @RequestParam Integer flag) | |||||
| throws DockerException, InterruptedException { | |||||
| logger.info("imageName: {}, imageID: {}, flag: {}", imageName, imageID, flag); | |||||
| JSONObject result = new JSONObject(); | |||||
| DockerClient dockerMaster = ClientUtil.getDocker(); | |||||
| List<DockerClient> dockerDeputies = ClientUtil.getDockerDeputies(); | |||||
| if (flag == 0) { | |||||
| // 选择更新 | |||||
| dockerService.syncImage(dockerMaster, dockerDeputies, imageName); | |||||
| } else { | |||||
| // 选择回退 | |||||
| dockerService.resetImage(dockerMaster, imageID, imageName); | |||||
| } | |||||
| result.put("code", 0); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,347 @@ | |||||
| 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; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,56 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.service.K8sService; | |||||
| import io.fabric8.kubernetes.client.KubernetesClient; | |||||
| import io.swagger.annotations.Api; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.http.MediaType; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import java.util.List; | |||||
| /** | |||||
| * Created by liqiankun on 2017/11/16 0016 | |||||
| * Description: | |||||
| */ | |||||
| @Api(value = "资源监控", hidden = true) | |||||
| @RestController | |||||
| @RequestMapping("/monitor") | |||||
| public class MonitorController { | |||||
| Logger logger = LoggerFactory.getLogger(MonitorController.class); | |||||
| @Autowired | |||||
| private K8sService k8sService; | |||||
| /** | |||||
| * 简单地获取集群中的Pod总量及各个节点上的Pod分布 | |||||
| * | |||||
| * @return | |||||
| * @throws Exception | |||||
| */ | |||||
| @RequestMapping(path = "/getPodsInfo") | |||||
| @ApiOperation(value = "获取集群中某个节点的信息", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject podsInfo() throws Exception { | |||||
| int podSum = k8sService.getPodSum(); | |||||
| List<String> nodeIP = k8sService.getNodesIP(); | |||||
| //生成Json格式的返回结果 | |||||
| StringBuffer jsonResult = new StringBuffer("["); | |||||
| for (String ip : nodeIP) { | |||||
| int num = k8sService.getPodNum(ip); | |||||
| jsonResult.append("{\"ip\":" + "\"" + ip + "\"," + "\"num\":" + "\"" + num + "\"},"); | |||||
| } | |||||
| int length = jsonResult.length() - 1; | |||||
| jsonResult.deleteCharAt(length); | |||||
| jsonResult.append("]"); | |||||
| JSONObject response = new JSONObject(); | |||||
| response.put("sum", podSum); | |||||
| response.put("distr", jsonResult); | |||||
| response.put("code", 0); | |||||
| return response; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.service.VncService; | |||||
| import com.educoder.bridge.utils.Base64Util; | |||||
| import com.educoder.bridge.utils.JedisUtil; | |||||
| import com.educoder.bridge.utils.PortUtil; | |||||
| import io.swagger.annotations.Api; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import io.swagger.annotations.ApiParam; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.http.MediaType; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RequestParam; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| @Api(value = "vnc控制器", hidden = true) | |||||
| @RestController | |||||
| @RequestMapping("/vnc") | |||||
| public class VncController { | |||||
| @Autowired | |||||
| private VncService vncService; | |||||
| private static final Logger logger = LoggerFactory.getLogger(VncController.class); | |||||
| @RequestMapping(path = "/getvnc") | |||||
| @ApiOperation(value = "图形界面", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| public JSONObject getvnc( | |||||
| @ApiParam(name = "tpiID", required = true, value = "实训实例的ID") @RequestParam String tpiID, | |||||
| @ApiParam(name = "containers", required = true, value = "需要使用的容器,base64编码") @RequestParam String containers) | |||||
| throws Exception{ | |||||
| JSONObject response = new JSONObject(); | |||||
| String podName = "vnc-" + tpiID; | |||||
| containers = Base64Util.decode(containers); | |||||
| // 为vnc分配port | |||||
| String port = JedisUtil.hget("port", podName); | |||||
| if (port == null) { | |||||
| port = PortUtil.getPort() + ""; | |||||
| JedisUtil.hset("port", podName, port); | |||||
| } | |||||
| vncService.getvnc(tpiID, containers); | |||||
| response.put("code", 0); | |||||
| response.put("port", port); | |||||
| return response; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,75 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.model.Webssh; | |||||
| import com.educoder.bridge.service.WebsshService; | |||||
| import com.educoder.bridge.utils.Base64Util; | |||||
| import io.swagger.annotations.Api; | |||||
| import io.swagger.annotations.ApiOperation; | |||||
| import io.swagger.annotations.ApiParam; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.http.MediaType; | |||||
| 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 org.springframework.web.servlet.ModelAndView; | |||||
| /** | |||||
| * @author guange | |||||
| * @date 2017/08/02 | |||||
| */ | |||||
| @Api(value = "提供webssh连接", hidden = true) | |||||
| @RestController | |||||
| public class WebsshController extends BaseController { | |||||
| private final static Logger logger = LoggerFactory.getLogger(WebsshController.class); | |||||
| @Autowired | |||||
| private WebsshService websshService; | |||||
| @ApiOperation(value = "获取连接容器所需的ip和端口信息", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||||
| @RequestMapping(path = "/webssh/getConnectInfo") | |||||
| public JSONObject getConnectInfo( | |||||
| @ApiParam(name = "tpiID", required = true, value = "tpiID") @RequestParam String tpiID, | |||||
| @ApiParam(name = "podType", required = true, value = "pod类型,0为evaluate, 1为webssh,2为evassh") @RequestParam Integer podType, | |||||
| @ApiParam(name = "containers", required = true, value = "需要使用的容器,base64编码") @RequestParam String containers) | |||||
| throws Exception { | |||||
| logger.info("tpiID: {}, podType: {},containers: {}", tpiID, podType, containers); | |||||
| containers = Base64Util.decode(containers); | |||||
| Webssh sshInfo = websshService.create(tpiID, podType, containers); | |||||
| JSONObject response = new JSONObject(); | |||||
| response.put("address", sshInfo.getAddress()); | |||||
| response.put("port", Integer.parseInt(sshInfo.getPort()) + ""); | |||||
| response.put("code", 0); | |||||
| return response; | |||||
| } | |||||
| @RequestMapping(value={"/", "ssh"}, method= RequestMethod.GET) | |||||
| public ModelAndView index(@RequestParam("Host")String host, | |||||
| @RequestParam("Port")int port, | |||||
| @RequestParam("Gameid")int gameId, | |||||
| @RequestParam("Username")String username, | |||||
| @RequestParam("Password")String password, | |||||
| @RequestParam("Tab")String tab, | |||||
| @RequestParam("Rows")int rows) { | |||||
| //index就是视图的名称(index.ftl) | |||||
| logger.debug("/ssh: 接收到前端连接请求,host: {}, port: {}", host, port); | |||||
| ModelAndView mv = new ModelAndView(); | |||||
| mv.setViewName("index"); | |||||
| mv.addObject("Host", host); | |||||
| mv.addObject("Port", port); | |||||
| mv.addObject("Username", username); | |||||
| mv.addObject("Password", password); | |||||
| mv.addObject("Tab", tab); | |||||
| mv.addObject("Rows", rows); | |||||
| mv.addObject("Gameid", gameId); | |||||
| mv.addObject("digest", System.currentTimeMillis()); | |||||
| return mv; | |||||
| } | |||||
| } | |||||