| 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; | |||
| } | |||
| } | |||