diff --git a/controller/BaseController.java b/controller/BaseController.java new file mode 100644 index 000000000..5fafd7d96 --- /dev/null +++ b/controller/BaseController.java @@ -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(); + } +} diff --git a/controller/DataTransferController.java b/controller/DataTransferController.java new file mode 100644 index 000000000..c0b284914 --- /dev/null +++ b/controller/DataTransferController.java @@ -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 + "/"); + + } +} diff --git a/controller/DockerController.java b/controller/DockerController.java new file mode 100644 index 000000000..f52561b37 --- /dev/null +++ b/controller/DockerController.java @@ -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 imageList = dockerService.getImageList(docker); + result.put("images", imageList); + + // 获取未同步的镜像 + List dockerDeputies = ClientUtil.getDockerDeputies(); + List 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 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 dockerDeputies = ClientUtil.getDockerDeputies(); + + if (flag == 0) { + // 选择更新 + dockerService.syncImage(dockerMaster, dockerDeputies, imageName); + } else { + // 选择回退 + dockerService.resetImage(dockerMaster, imageID, imageName); + } + + result.put("code", 0); + return result; + } + +} diff --git a/controller/GameController.java b/controller/GameController.java new file mode 100644 index 000000000..9f92d5076 --- /dev/null +++ b/controller/GameController.java @@ -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; + } + +} diff --git a/controller/MonitorController.java b/controller/MonitorController.java new file mode 100644 index 000000000..700a460b4 --- /dev/null +++ b/controller/MonitorController.java @@ -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 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; + } +} diff --git a/controller/VncController.java b/controller/VncController.java new file mode 100644 index 000000000..23c9222e2 --- /dev/null +++ b/controller/VncController.java @@ -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; + } +} diff --git a/controller/WebsshController.java b/controller/WebsshController.java new file mode 100644 index 000000000..047551e48 --- /dev/null +++ b/controller/WebsshController.java @@ -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; + } + +}