From 2e87fae9f8c8a7c4531aac7c38bd82d1da5ec436 Mon Sep 17 00:00:00 2001 From: educoder Date: Mon, 18 Jul 2022 11:21:52 +0800 Subject: [PATCH] first --- .gitignore | 2 + common/common.iml | 218 +++ common/pom.xml | 36 + .../advisor/DefaultControllerAdvisor.java | 73 + .../imitate/common/annotation/PublicUrl.java | 19 + .../com/imitate/common/bean/ApiResult.java | 61 + .../com/imitate/common/bean/BeanFactory.java | 44 + .../com/imitate/common/bean/BridgePage.java | 53 + .../com/imitate/common/bean/ShellResult.java | 48 + .../com/imitate/common/config/DateConfig.java | 141 ++ .../imitate/common/config/InitListener.java | 36 + .../com/imitate/common/config/JdbcConfig.java | 35 + .../imitate/common/config/MybatisConfig.java | 9 + .../common/config/RedisListenerConfig.java | 21 + .../UncaughtBridgeExceptionHandler.java | 23 + .../imitate/common/config/WebMvcConfig.java | 63 + .../common/constant/ApiResultCsts.java | 7 + .../common/constant/BuildResultCsts.java | 76 + .../com/imitate/common/constant/TpCsts.java | 217 +++ .../imitate/common/enums/CommonStateEnum.java | 53 + .../imitate/common/enums/ErrorCodeEnum.java | 60 + .../common/exception/BusinessException.java | 36 + .../interceptor/HttpServletRequestFilter.java | 117 ++ .../common/interceptor/SignInterceptor.java | 142 ++ .../common/k8s/bean/BridgeContainer.java | 73 + .../imitate/common/k8s/bean/BridgeNode.java | 47 + .../imitate/common/k8s/bean/BuildResult.java | 231 +++ .../imitate/common/k8s/bean/ClusterInfo.java | 384 +++++ .../k8s/bean/ContainerCreateParams.java | 143 ++ .../common/k8s/bean/ContainerMountParams.java | 28 + .../common/k8s/bean/CreatePodResult.java | 66 + .../k8s/bean/DeploymentCreateParams.java | 38 + .../common/k8s/bean/ExecResultCase.java | 122 ++ .../common/k8s/bean/HPACreateParams.java | 46 + .../common/k8s/bean/NodeEvaErrorInfo.java | 77 + .../common/k8s/bean/NodeQueryParam.java | 35 + .../com/imitate/common/k8s/bean/NodeRes.java | 58 + .../imitate/common/k8s/bean/NodeResStat.java | 76 + .../imitate/common/k8s/bean/OjEvaResult.java | 165 ++ .../common/k8s/bean/PodCreateParams.java | 41 + .../common/k8s/bean/PodCreateStrategy.java | 123 ++ .../common/k8s/bean/PodQueryParam.java | 281 ++++ .../common/k8s/bean/RunPodQueryParam.java | 127 ++ .../common/k8s/bean/ServiceCreateParams.java | 42 + .../com/imitate/common/k8s/bean/TimeCost.java | 81 + .../common/k8s/constant/BridgeNodeCsts.java | 8 + .../common/k8s/constant/BridgePodCsts.java | 8 + .../k8s/constant/PlatformConfigCsts.java | 12 + .../common/k8s/mapper/BridgePodMapper.java | 46 + .../common/k8s/mapper/ErrorPodInfoMapper.java | 14 + .../common/k8s/mapper/EvaDayStatMapper.java | 21 + .../common/k8s/mapper/OjEvaDayStatMapper.java | 16 + .../k8s/mapper/PlatformConfigMapper.java | 22 + .../common/k8s/mapper/RunPodMapper.java | 55 + .../mapper/SecurityContextConfigMapper.java | 16 + .../common/k8s/mapper/WindowsInfoMapper.java | 31 + .../common/k8s/mgr/ClusterManager.java | 173 ++ .../imitate/common/k8s/mgr/NodeManager.java | 146 ++ .../common/k8s/mgr/node/GpuNodeMgr.java | 89 + .../common/k8s/mgr/node/NodeMgrIf.java | 48 + .../common/k8s/mgr/node/NormalNodeMgr.java | 85 + .../common/k8s/mgr/node/OjNodeMgr.java | 107 ++ .../common/k8s/mgr/node/ShenlongNodeMgr.java | 89 + .../mgr/node/cluster/ClusterGpuNodeMgr.java | 311 ++++ .../mgr/node/cluster/ClusterNodeMgrIf.java | 61 + .../node/cluster/ClusterNormalNodeMgr.java | 114 ++ .../mgr/node/cluster/ClusterOjNodeMgr.java | 348 ++++ .../node/cluster/ClusterShenlongNodeMgr.java | 299 ++++ .../imitate/common/k8s/pojo/BridgePod.java | 67 + .../imitate/common/k8s/pojo/ErrorPodInfo.java | 40 + .../imitate/common/k8s/pojo/EvaDayStat.java | 74 + .../imitate/common/k8s/pojo/OjEvaDayStat.java | 56 + .../common/k8s/pojo/PlatformConfig.java | 29 + .../com/imitate/common/k8s/pojo/RunPod.java | 78 + .../k8s/pojo/SecurityContextConfig.java | 30 + .../imitate/common/k8s/pojo/WindowsInfo.java | 44 + .../common/k8s/service/BridgeNodeService.java | 71 + .../common/k8s/service/BridgePodService.java | 119 ++ .../common/k8s/service/DiskService.java | 157 ++ .../common/k8s/service/EvaDayStatService.java | 19 + .../common/k8s/service/K8sService.java | 1330 +++++++++++++++ .../k8s/service/NodeResUsageService.java | 120 ++ .../k8s/service/OjEvaDayStatService.java | 54 + .../k8s/service/PlatformConfigService.java | 59 + .../common/k8s/service/PortService.java | 110 ++ .../common/k8s/service/RunPodService.java | 511 ++++++ .../k8s/service/SecurityContextService.java | 42 + .../common/k8s/util/ContainerUtil.java | 227 +++ .../k8s/util/GetPodErrorReasonUtil.java | 30 + .../common/k8s/util/K8sClientUtil.java | 93 ++ .../k8s/util/K8sPodCommandExecutor.java | 119 ++ .../com/imitate/common/k8s/util/K8sUtils.java | 371 +++++ .../common/shiro/config/FilterConfig.java | 22 + .../common/shiro/config/OAuth2Filter.java | 108 ++ .../common/shiro/config/ShiroConfig.java | 106 ++ .../common/shiro/mapper/SysMenuMapper.java | 16 + .../common/shiro/mapper/SysRoleMapper.java | 31 + .../shiro/mapper/SysRoleMenuMapper.java | 29 + .../common/shiro/mapper/SysTokenMapper.java | 14 + .../common/shiro/mapper/SysUserMapper.java | 23 + .../shiro/mapper/SysUserRoleMapper.java | 37 + .../imitate/common/shiro/pojo/SysMenu.java | 57 + .../imitate/common/shiro/pojo/SysRole.java | 46 + .../common/shiro/pojo/SysRoleMenu.java | 29 + .../imitate/common/shiro/pojo/SysToken.java | 23 + .../imitate/common/shiro/pojo/SysUser.java | 38 + .../common/shiro/pojo/SysUserRole.java | 29 + .../common/shiro/realm/OAuth2Realm.java | 81 + .../common/shiro/realm/OAuth2Token.java | 26 + .../common/shiro/service/ShiroService.java | 107 ++ .../common/sys/competitor/LockCompetitor.java | 19 + .../competitor/PodScheduleLockCompetitor.java | 122 ++ .../common/sys/constant/SysConfigCsts.java | 457 +++++ .../sys/mapper/ClusterConfigMapper.java | 30 + .../common/sys/mapper/SysConfigMapper.java | 32 + .../common/sys/pojo/ClusterConfig.java | 108 ++ .../imitate/common/sys/pojo/SysConfig.java | 29 + .../sys/service/ClusterConfigService.java | 141 ++ .../sys/service/ResourceFileService.java | 36 + .../common/sys/service/SysConfigService.java | 670 ++++++++ .../common/sys/settings/AppConfig.java | 436 +++++ .../common/task/DelHisK8sNodeImageTask.java | 90 + .../imitate/common/task/DelMissPodTask.java | 104 ++ .../common/task/init/DelExpiredPodTask.java | 117 ++ .../common/task/refresh/ClusterTask.java | 56 + .../task/refresh/NodeErrorCheckTask.java | 150 ++ .../task/refresh/NodeStatusCheckTask.java | 275 +++ .../task/refresh/PlatformConfigTask.java | 36 + .../common/task/refresh/ResUsageTask.java | 36 + .../refresh/SecurityContextConfigTask.java | 36 + .../imitate/common/task/refresh/SysTask.java | 36 + .../test/MyStarterAutoConfiguration.java | 27 + .../java/com/imitate/common/test/Person.java | 11 + .../common/test/PersonConfigProperties.java | 13 + .../com/imitate/common/util/AbstractDO.java | 34 + .../com/imitate/common/util/Base64Util.java | 91 + .../com/imitate/common/util/BaseMapper.java | 13 + .../common/util/BasePageCondition.java | 52 + .../imitate/common/util/BasicController.java | 34 + .../com/imitate/common/util/FileUtil.java | 301 ++++ .../com/imitate/common/util/GameHelper.java | 358 ++++ .../imitate/common/util/HttpContextUtil.java | 73 + .../imitate/common/util/HttpContextUtils.java | 29 + .../com/imitate/common/util/HttpHelper.java | 327 ++++ .../java/com/imitate/common/util/IpUtil.java | 50 + .../java/com/imitate/common/util/IpUtils.java | 75 + .../com/imitate/common/util/JedisUtil.java | 635 +++++++ .../com/imitate/common/util/JsonUtils.java | 49 + .../java/com/imitate/common/util/MD5Util.java | 51 + .../imitate/common/util/OKHttp3Utils3.java | 313 ++++ .../imitate/common/util/OjEvaResultUtils.java | 24 + .../common/util/ProcessStatusUtil.java | 21 + .../main/java/com/imitate/common/util/R.java | 81 + .../com/imitate/common/util/RedisPool.java | 74 + .../imitate/common/util/ShellExeCallBack.java | 12 + .../com/imitate/common/util/ShellUtil.java | 179 ++ .../com/imitate/common/util/SignUtil.java | 140 ++ .../com/imitate/common/util/StringUtil.java | 17 + .../com/imitate/common/util/ThreadUtils.java | 18 + .../imitate/common/util/TimeCostUtils.java | 62 + .../com/imitate/common/util/TimeHelper.java | 44 + .../java/com/imitate/common/util/TpUtils.java | 153 ++ .../common/util/compile/CompileUtils.java | 39 + .../com/imitate/common/util/id/IdUtils.java | 20 + .../common/util/id/SnowflakeIdWorker.java | 147 ++ .../com/imitate/common/util/net/NetUtils.java | 64 + .../imitate/common/util/net/SocketUtils.java | 25 + .../imitate/common/util/net/TelnetUtils.java | 29 + .../main/resources/META-INF/spring.factories | 3 + common/src/main/resources/common.properties | 142 ++ common/src/main/resources/cron.properties | 48 + .../resources/mybatis/BridgePodMapper.xml | 535 ++++++ .../resources/mybatis/ClusterConfigMapper.xml | 349 ++++ .../resources/mybatis/ErrorPodInfoMapper.xml | 164 ++ .../resources/mybatis/EvaDayStatMapper.xml | 113 ++ .../resources/mybatis/OjEvaDayStatMapper.xml | 67 + .../mybatis/PlatformConfigMapper.xml | 110 ++ .../main/resources/mybatis/RunPodMapper.xml | 401 +++++ .../mybatis/SecurityContextConfigMapper.xml | 24 + .../resources/mybatis/SysConfigMapper.xml | 161 ++ .../main/resources/mybatis/SysMenuMapper.xml | 7 + .../main/resources/mybatis/SysRoleMapper.xml | 36 + .../resources/mybatis/SysRoleMenuMapper.xml | 17 + .../main/resources/mybatis/SysTokenMapper.xml | 20 + .../main/resources/mybatis/SysUserMapper.xml | 40 + .../resources/mybatis/SysUserRoleMapper.xml | 39 + .../resources/mybatis/WindowsInfoMapper.xml | 191 +++ .../spring-configuration-metadata.json | 27 + common/target/classes/common.properties | 142 ++ common/target/classes/cron.properties | 48 + .../classes/mybatis/BridgePodMapper.xml | 535 ++++++ .../classes/mybatis/ClusterConfigMapper.xml | 349 ++++ .../classes/mybatis/ErrorPodInfoMapper.xml | 164 ++ .../classes/mybatis/EvaDayStatMapper.xml | 113 ++ .../classes/mybatis/OjEvaDayStatMapper.xml | 67 + .../classes/mybatis/PlatformConfigMapper.xml | 110 ++ .../target/classes/mybatis/RunPodMapper.xml | 401 +++++ .../mybatis/SecurityContextConfigMapper.xml | 24 + .../classes/mybatis/SysConfigMapper.xml | 161 ++ .../target/classes/mybatis/SysMenuMapper.xml | 7 + .../target/classes/mybatis/SysRoleMapper.xml | 36 + .../classes/mybatis/SysRoleMenuMapper.xml | 17 + .../target/classes/mybatis/SysTokenMapper.xml | 20 + .../target/classes/mybatis/SysUserMapper.xml | 40 + .../classes/mybatis/SysUserRoleMapper.xml | 39 + .../classes/mybatis/WindowsInfoMapper.xml | 191 +++ parent/parent.iml | 200 +++ parent/pom.xml | 311 ++++ pom.xml | 19 + web/pom.xml | 30 + .../java/com/imitate/web/WebApplication.java | 24 + .../imitate/web/aspect/ParamsOutAspect.java | 166 ++ .../example/controller/UserController.java | 96 ++ .../game/comparator/ClusterInfoCmp.java | 105 ++ .../web/module/game/config/PoolConfig.java | 61 + .../game/controller/GameController.java | 220 +++ .../game/controller/VscodeController.java | 185 +++ .../module/game/enums/GitUrlComponent.java | 35 + .../module/game/pojo/EvaErrorResUsage.java | 101 ++ .../web/module/game/pojo/EvaStepOut.java | 27 + .../web/module/game/pojo/ExecSourceCase.java | 33 + .../module/game/pojo/GitPullRequestParam.java | 108 ++ .../game/pojo/GitResetRequestParam.java | 68 + .../web/module/game/pojo/LocalCheck.java | 40 + .../web/module/game/pojo/ResUsage.java | 48 + .../module/game/pojo/SimpleBuildResult.java | 54 + .../web/module/game/pojo/TimeoutResult.java | 31 + .../game/service/ClusterScheduleMgr.java | 266 +++ .../module/game/service/EducoderService.java | 63 + .../game/service/EvaTimeoutService.java | 151 ++ .../game/service/ExecResultService.java | 66 + .../web/module/game/service/GameService.java | 1469 +++++++++++++++++ .../game/service/GitAccountService.java | 55 + .../web/module/game/service/GitService.java | 358 ++++ .../web/module/game/service/ImageService.java | 90 + .../game/service/KubernetesService.java | 264 +++ .../game/service/MqttCallBackService.java | 49 + .../game/service/ShellExeCallBackService.java | 41 + .../module/game/service/TestCaseService.java | 123 ++ .../module/game/service/VscodeService.java | 150 ++ .../game/thread/BridgeThreadPoolExecutor.java | 89 + .../thread/BridgeThreadPoolTaskExecutor.java | 98 ++ .../game/thread/LogEnhancementRunnable.java | 41 + .../game/thread/code/LocalCheckJob.java | 44 + .../web/module/game/thread/eva/EvaJob.java | 17 + .../web/module/game/thread/eva/TpEvaJob.java | 369 +++++ .../web/module/game/util/AviatorUtils.java | 32 + .../web/module/game/util/ListCmpFunction.java | 99 ++ .../module/task/DelHisCreateImageTask.java | 49 + .../imitate/web/module/task/GitInitTask.java | 29 + .../web/module/task/ScriptInitTask.java | 53 + .../com/imitate/web/params/DemoParam.java | 20 + .../imitate/web/params/GameEvaluateParam.java | 139 ++ .../imitate/web/params/GetVsCodeParam.java | 27 + .../imitate/web/params/VsCodeDeleteParam.java | 24 + .../imitate/web/persistence/beans/SysLog.java | 216 +++ .../web/persistence/beans/Windows.java | 54 + .../web/persistence/mapper/WindowsMapper.java | 11 + .../com/imitate/web/vo/GameEvaluateVO.java | 17 + .../imitate/web/vo/WindowsInfoDelegate.java | 50 + web/src/main/resources/application.properties | 56 + web/src/main/resources/evaluate/evaluate.sh | 48 + web/src/main/resources/evaluate/execEva.py | 105 ++ web/src/main/resources/evaluate/execEva.sh | 12 + .../main/resources/evaluate/execStepOut.sh | 186 +++ web/src/main/resources/evaluate/kill.sh | 14 + web/src/main/resources/logback-spring.xml | 183 ++ web/target/classes/application.properties | 56 + web/target/classes/evaluate/evaluate.sh | 48 + web/target/classes/evaluate/execEva.py | 105 ++ web/target/classes/evaluate/execEva.sh | 12 + web/target/classes/evaluate/execStepOut.sh | 186 +++ web/target/classes/evaluate/kill.sh | 14 + web/target/classes/logback-spring.xml | 183 ++ web/web.iml | 218 +++ 275 files changed, 30335 insertions(+) create mode 100644 common/common.iml create mode 100644 common/pom.xml create mode 100644 common/src/main/java/com/imitate/common/advisor/DefaultControllerAdvisor.java create mode 100644 common/src/main/java/com/imitate/common/annotation/PublicUrl.java create mode 100644 common/src/main/java/com/imitate/common/bean/ApiResult.java create mode 100644 common/src/main/java/com/imitate/common/bean/BeanFactory.java create mode 100644 common/src/main/java/com/imitate/common/bean/BridgePage.java create mode 100644 common/src/main/java/com/imitate/common/bean/ShellResult.java create mode 100644 common/src/main/java/com/imitate/common/config/DateConfig.java create mode 100644 common/src/main/java/com/imitate/common/config/InitListener.java create mode 100644 common/src/main/java/com/imitate/common/config/JdbcConfig.java create mode 100644 common/src/main/java/com/imitate/common/config/MybatisConfig.java create mode 100644 common/src/main/java/com/imitate/common/config/RedisListenerConfig.java create mode 100644 common/src/main/java/com/imitate/common/config/UncaughtBridgeExceptionHandler.java create mode 100644 common/src/main/java/com/imitate/common/config/WebMvcConfig.java create mode 100644 common/src/main/java/com/imitate/common/constant/ApiResultCsts.java create mode 100644 common/src/main/java/com/imitate/common/constant/BuildResultCsts.java create mode 100644 common/src/main/java/com/imitate/common/constant/TpCsts.java create mode 100644 common/src/main/java/com/imitate/common/enums/CommonStateEnum.java create mode 100644 common/src/main/java/com/imitate/common/enums/ErrorCodeEnum.java create mode 100644 common/src/main/java/com/imitate/common/exception/BusinessException.java create mode 100644 common/src/main/java/com/imitate/common/interceptor/HttpServletRequestFilter.java create mode 100644 common/src/main/java/com/imitate/common/interceptor/SignInterceptor.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/BridgeContainer.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/BridgeNode.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/BuildResult.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/ClusterInfo.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/ContainerCreateParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/ContainerMountParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/CreatePodResult.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/DeploymentCreateParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/ExecResultCase.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/HPACreateParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/NodeEvaErrorInfo.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/NodeQueryParam.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/NodeRes.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/NodeResStat.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/OjEvaResult.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/PodCreateParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/PodCreateStrategy.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/PodQueryParam.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/RunPodQueryParam.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/ServiceCreateParams.java create mode 100644 common/src/main/java/com/imitate/common/k8s/bean/TimeCost.java create mode 100644 common/src/main/java/com/imitate/common/k8s/constant/BridgeNodeCsts.java create mode 100644 common/src/main/java/com/imitate/common/k8s/constant/BridgePodCsts.java create mode 100644 common/src/main/java/com/imitate/common/k8s/constant/PlatformConfigCsts.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/BridgePodMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/ErrorPodInfoMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/EvaDayStatMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/OjEvaDayStatMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/PlatformConfigMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/RunPodMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/SecurityContextConfigMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mapper/WindowsInfoMapper.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/ClusterManager.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/NodeManager.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/GpuNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/NodeMgrIf.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/NormalNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/OjNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/ShenlongNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterGpuNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNodeMgrIf.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNormalNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterOjNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterShenlongNodeMgr.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/BridgePod.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/ErrorPodInfo.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/EvaDayStat.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/OjEvaDayStat.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/PlatformConfig.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/RunPod.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/SecurityContextConfig.java create mode 100644 common/src/main/java/com/imitate/common/k8s/pojo/WindowsInfo.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/BridgeNodeService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/BridgePodService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/DiskService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/EvaDayStatService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/K8sService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/NodeResUsageService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/OjEvaDayStatService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/PlatformConfigService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/PortService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/RunPodService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/service/SecurityContextService.java create mode 100644 common/src/main/java/com/imitate/common/k8s/util/ContainerUtil.java create mode 100644 common/src/main/java/com/imitate/common/k8s/util/GetPodErrorReasonUtil.java create mode 100644 common/src/main/java/com/imitate/common/k8s/util/K8sClientUtil.java create mode 100644 common/src/main/java/com/imitate/common/k8s/util/K8sPodCommandExecutor.java create mode 100644 common/src/main/java/com/imitate/common/k8s/util/K8sUtils.java create mode 100644 common/src/main/java/com/imitate/common/shiro/config/FilterConfig.java create mode 100644 common/src/main/java/com/imitate/common/shiro/config/OAuth2Filter.java create mode 100644 common/src/main/java/com/imitate/common/shiro/config/ShiroConfig.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysMenuMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMenuMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysTokenMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysUserMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/mapper/SysUserRoleMapper.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysMenu.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysRole.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysRoleMenu.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysToken.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysUser.java create mode 100644 common/src/main/java/com/imitate/common/shiro/pojo/SysUserRole.java create mode 100644 common/src/main/java/com/imitate/common/shiro/realm/OAuth2Realm.java create mode 100644 common/src/main/java/com/imitate/common/shiro/realm/OAuth2Token.java create mode 100644 common/src/main/java/com/imitate/common/shiro/service/ShiroService.java create mode 100644 common/src/main/java/com/imitate/common/sys/competitor/LockCompetitor.java create mode 100644 common/src/main/java/com/imitate/common/sys/competitor/PodScheduleLockCompetitor.java create mode 100644 common/src/main/java/com/imitate/common/sys/constant/SysConfigCsts.java create mode 100644 common/src/main/java/com/imitate/common/sys/mapper/ClusterConfigMapper.java create mode 100644 common/src/main/java/com/imitate/common/sys/mapper/SysConfigMapper.java create mode 100644 common/src/main/java/com/imitate/common/sys/pojo/ClusterConfig.java create mode 100644 common/src/main/java/com/imitate/common/sys/pojo/SysConfig.java create mode 100644 common/src/main/java/com/imitate/common/sys/service/ClusterConfigService.java create mode 100644 common/src/main/java/com/imitate/common/sys/service/ResourceFileService.java create mode 100644 common/src/main/java/com/imitate/common/sys/service/SysConfigService.java create mode 100644 common/src/main/java/com/imitate/common/sys/settings/AppConfig.java create mode 100644 common/src/main/java/com/imitate/common/task/DelHisK8sNodeImageTask.java create mode 100644 common/src/main/java/com/imitate/common/task/DelMissPodTask.java create mode 100644 common/src/main/java/com/imitate/common/task/init/DelExpiredPodTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/ClusterTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/NodeErrorCheckTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/NodeStatusCheckTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/PlatformConfigTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/ResUsageTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/SecurityContextConfigTask.java create mode 100644 common/src/main/java/com/imitate/common/task/refresh/SysTask.java create mode 100644 common/src/main/java/com/imitate/common/test/MyStarterAutoConfiguration.java create mode 100644 common/src/main/java/com/imitate/common/test/Person.java create mode 100644 common/src/main/java/com/imitate/common/test/PersonConfigProperties.java create mode 100644 common/src/main/java/com/imitate/common/util/AbstractDO.java create mode 100644 common/src/main/java/com/imitate/common/util/Base64Util.java create mode 100644 common/src/main/java/com/imitate/common/util/BaseMapper.java create mode 100644 common/src/main/java/com/imitate/common/util/BasePageCondition.java create mode 100644 common/src/main/java/com/imitate/common/util/BasicController.java create mode 100644 common/src/main/java/com/imitate/common/util/FileUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/GameHelper.java create mode 100644 common/src/main/java/com/imitate/common/util/HttpContextUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/HttpContextUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/HttpHelper.java create mode 100644 common/src/main/java/com/imitate/common/util/IpUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/IpUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/JedisUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/JsonUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/MD5Util.java create mode 100644 common/src/main/java/com/imitate/common/util/OKHttp3Utils3.java create mode 100644 common/src/main/java/com/imitate/common/util/OjEvaResultUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/ProcessStatusUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/R.java create mode 100644 common/src/main/java/com/imitate/common/util/RedisPool.java create mode 100644 common/src/main/java/com/imitate/common/util/ShellExeCallBack.java create mode 100644 common/src/main/java/com/imitate/common/util/ShellUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/SignUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/StringUtil.java create mode 100644 common/src/main/java/com/imitate/common/util/ThreadUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/TimeCostUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/TimeHelper.java create mode 100644 common/src/main/java/com/imitate/common/util/TpUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/compile/CompileUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/id/IdUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/id/SnowflakeIdWorker.java create mode 100644 common/src/main/java/com/imitate/common/util/net/NetUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/net/SocketUtils.java create mode 100644 common/src/main/java/com/imitate/common/util/net/TelnetUtils.java create mode 100644 common/src/main/resources/META-INF/spring.factories create mode 100644 common/src/main/resources/common.properties create mode 100644 common/src/main/resources/cron.properties create mode 100644 common/src/main/resources/mybatis/BridgePodMapper.xml create mode 100644 common/src/main/resources/mybatis/ClusterConfigMapper.xml create mode 100644 common/src/main/resources/mybatis/ErrorPodInfoMapper.xml create mode 100644 common/src/main/resources/mybatis/EvaDayStatMapper.xml create mode 100644 common/src/main/resources/mybatis/OjEvaDayStatMapper.xml create mode 100644 common/src/main/resources/mybatis/PlatformConfigMapper.xml create mode 100644 common/src/main/resources/mybatis/RunPodMapper.xml create mode 100644 common/src/main/resources/mybatis/SecurityContextConfigMapper.xml create mode 100644 common/src/main/resources/mybatis/SysConfigMapper.xml create mode 100644 common/src/main/resources/mybatis/SysMenuMapper.xml create mode 100644 common/src/main/resources/mybatis/SysRoleMapper.xml create mode 100644 common/src/main/resources/mybatis/SysRoleMenuMapper.xml create mode 100644 common/src/main/resources/mybatis/SysTokenMapper.xml create mode 100644 common/src/main/resources/mybatis/SysUserMapper.xml create mode 100644 common/src/main/resources/mybatis/SysUserRoleMapper.xml create mode 100644 common/src/main/resources/mybatis/WindowsInfoMapper.xml create mode 100644 common/target/classes/META-INF/spring-configuration-metadata.json create mode 100644 common/target/classes/common.properties create mode 100644 common/target/classes/cron.properties create mode 100644 common/target/classes/mybatis/BridgePodMapper.xml create mode 100644 common/target/classes/mybatis/ClusterConfigMapper.xml create mode 100644 common/target/classes/mybatis/ErrorPodInfoMapper.xml create mode 100644 common/target/classes/mybatis/EvaDayStatMapper.xml create mode 100644 common/target/classes/mybatis/OjEvaDayStatMapper.xml create mode 100644 common/target/classes/mybatis/PlatformConfigMapper.xml create mode 100644 common/target/classes/mybatis/RunPodMapper.xml create mode 100644 common/target/classes/mybatis/SecurityContextConfigMapper.xml create mode 100644 common/target/classes/mybatis/SysConfigMapper.xml create mode 100644 common/target/classes/mybatis/SysMenuMapper.xml create mode 100644 common/target/classes/mybatis/SysRoleMapper.xml create mode 100644 common/target/classes/mybatis/SysRoleMenuMapper.xml create mode 100644 common/target/classes/mybatis/SysTokenMapper.xml create mode 100644 common/target/classes/mybatis/SysUserMapper.xml create mode 100644 common/target/classes/mybatis/SysUserRoleMapper.xml create mode 100644 common/target/classes/mybatis/WindowsInfoMapper.xml create mode 100644 parent/parent.iml create mode 100644 parent/pom.xml create mode 100644 pom.xml create mode 100644 web/pom.xml create mode 100644 web/src/main/java/com/imitate/web/WebApplication.java create mode 100644 web/src/main/java/com/imitate/web/aspect/ParamsOutAspect.java create mode 100644 web/src/main/java/com/imitate/web/module/example/controller/UserController.java create mode 100644 web/src/main/java/com/imitate/web/module/game/comparator/ClusterInfoCmp.java create mode 100644 web/src/main/java/com/imitate/web/module/game/config/PoolConfig.java create mode 100644 web/src/main/java/com/imitate/web/module/game/controller/GameController.java create mode 100644 web/src/main/java/com/imitate/web/module/game/controller/VscodeController.java create mode 100644 web/src/main/java/com/imitate/web/module/game/enums/GitUrlComponent.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/EvaErrorResUsage.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/EvaStepOut.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/ExecSourceCase.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/GitPullRequestParam.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/GitResetRequestParam.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/LocalCheck.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/ResUsage.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/SimpleBuildResult.java create mode 100644 web/src/main/java/com/imitate/web/module/game/pojo/TimeoutResult.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/ClusterScheduleMgr.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/EducoderService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/EvaTimeoutService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/ExecResultService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/GameService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/GitAccountService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/GitService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/ImageService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/KubernetesService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/MqttCallBackService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/ShellExeCallBackService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/TestCaseService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/service/VscodeService.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolExecutor.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolTaskExecutor.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/LogEnhancementRunnable.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/code/LocalCheckJob.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/eva/EvaJob.java create mode 100644 web/src/main/java/com/imitate/web/module/game/thread/eva/TpEvaJob.java create mode 100644 web/src/main/java/com/imitate/web/module/game/util/AviatorUtils.java create mode 100644 web/src/main/java/com/imitate/web/module/game/util/ListCmpFunction.java create mode 100644 web/src/main/java/com/imitate/web/module/task/DelHisCreateImageTask.java create mode 100644 web/src/main/java/com/imitate/web/module/task/GitInitTask.java create mode 100644 web/src/main/java/com/imitate/web/module/task/ScriptInitTask.java create mode 100644 web/src/main/java/com/imitate/web/params/DemoParam.java create mode 100644 web/src/main/java/com/imitate/web/params/GameEvaluateParam.java create mode 100644 web/src/main/java/com/imitate/web/params/GetVsCodeParam.java create mode 100644 web/src/main/java/com/imitate/web/params/VsCodeDeleteParam.java create mode 100644 web/src/main/java/com/imitate/web/persistence/beans/SysLog.java create mode 100644 web/src/main/java/com/imitate/web/persistence/beans/Windows.java create mode 100644 web/src/main/java/com/imitate/web/persistence/mapper/WindowsMapper.java create mode 100644 web/src/main/java/com/imitate/web/vo/GameEvaluateVO.java create mode 100644 web/src/main/java/com/imitate/web/vo/WindowsInfoDelegate.java create mode 100644 web/src/main/resources/application.properties create mode 100644 web/src/main/resources/evaluate/evaluate.sh create mode 100644 web/src/main/resources/evaluate/execEva.py create mode 100644 web/src/main/resources/evaluate/execEva.sh create mode 100644 web/src/main/resources/evaluate/execStepOut.sh create mode 100644 web/src/main/resources/evaluate/kill.sh create mode 100644 web/src/main/resources/logback-spring.xml create mode 100644 web/target/classes/application.properties create mode 100644 web/target/classes/evaluate/evaluate.sh create mode 100644 web/target/classes/evaluate/execEva.py create mode 100644 web/target/classes/evaluate/execEva.sh create mode 100644 web/target/classes/evaluate/execStepOut.sh create mode 100644 web/target/classes/evaluate/kill.sh create mode 100644 web/target/classes/logback-spring.xml create mode 100644 web/web.iml diff --git a/.gitignore b/.gitignore index 84adb3f..05fdf1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # ---> Java # Compiled class file *.class +./.idea/ +.idea/ # Log file *.log diff --git a/common/common.iml b/common/common.iml new file mode 100644 index 0000000..f2c8bb2 --- /dev/null +++ b/common/common.iml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..a4488e8 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + parent + com.imitate + 0.0.1-SNAPSHOT + ../parent/pom.xml + + + common + 0.0.1-SNAPSHOT + common + common + jar + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-autoconfigure + + + + + + + + diff --git a/common/src/main/java/com/imitate/common/advisor/DefaultControllerAdvisor.java b/common/src/main/java/com/imitate/common/advisor/DefaultControllerAdvisor.java new file mode 100644 index 0000000..70418d6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/advisor/DefaultControllerAdvisor.java @@ -0,0 +1,73 @@ +package com.imitate.common.advisor; + +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.util.R; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.validation.ConstraintViolationException; + + +/** + * @author yanchao + */ +@RestControllerAdvice +public class DefaultControllerAdvisor { + + private static final Logger logger = LoggerFactory.getLogger(DefaultControllerAdvisor.class); + + @ExceptionHandler(Exception.class) + public R processException(Exception e) { + logger.error(e.getMessage(), e); + return R.error(ErrorCodeEnum.EXCEPTION.getValue(),ErrorCodeEnum.EXCEPTION.getDescription()); + } + + @ExceptionHandler(BusinessException.class) + public R processException(BusinessException e) { + logger.error(e.getMessage(), e); + return R.error(e.getErrCode(),e.getMessage()); + } + + + + @ExceptionHandler(MissingServletRequestParameterException.class) + public R processMissingServletRequestParameterException(MissingServletRequestParameterException e){ + logger.error(e.getMessage(), e); + return R.error(ErrorCodeEnum.MVC_BIND_EXCEPTION.getValue(),ErrorCodeEnum.MVC_BIND_EXCEPTION.getDescription()); + } + + + + @ExceptionHandler(ConstraintViolationException.class) + public R processConstraintViolationException(ConstraintViolationException e) { + logger.error(e.getMessage(), e); + return R.error(ErrorCodeEnum.INVALID_ARG_EXCEPTION.getValue(),ErrorCodeEnum.INVALID_ARG_EXCEPTION.getDescription()); + } + + + + @ExceptionHandler(BindException.class) + public R processBindException(BindException e) { + logger.error(e.getMessage(), e); + return R.error(ErrorCodeEnum.BIND_EXCEPTION.getValue(),ErrorCodeEnum.BIND_EXCEPTION.getDescription()); + } + + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public R processHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + logger.error(e.getMessage(), e); + return R.error(ErrorCodeEnum.METHOD_NOT_ALLOWED_EXCEPTION.getValue(),ErrorCodeEnum.METHOD_NOT_ALLOWED_EXCEPTION.getDescription()); + } + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/annotation/PublicUrl.java b/common/src/main/java/com/imitate/common/annotation/PublicUrl.java new file mode 100644 index 0000000..0708be2 --- /dev/null +++ b/common/src/main/java/com/imitate/common/annotation/PublicUrl.java @@ -0,0 +1,19 @@ +package com.imitate.common.annotation; + +import java.lang.annotation.*; + +/** + * 公开性自定义注解 + * @author yanchao + */ +@Inherited +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PublicUrl { + /** + * 是否校验签名合法性 + * @return + */ + boolean signValidate() default false; +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/bean/ApiResult.java b/common/src/main/java/com/imitate/common/bean/ApiResult.java new file mode 100644 index 0000000..8a95c6e --- /dev/null +++ b/common/src/main/java/com/imitate/common/bean/ApiResult.java @@ -0,0 +1,61 @@ +package com.imitate.common.bean; + + +import com.imitate.common.constant.ApiResultCsts; + +public class ApiResult { + + private int code = ApiResultCsts.CODE_SUCCESS; + private String msg; + + private T data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public ApiResult() { + } + + public ApiResult(int code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + } + + public static ApiResult successResult(T data) { + return successResult("success", data); + } + + public static ApiResult successResult(String msg, T data) { + return new ApiResult<>(0, msg, data); + } + + public static ApiResult failResult(int code, String msg, T data) { + return new ApiResult<>(code, msg, data); + } + + public static ApiResult failResult(int code, String msg) { + return new ApiResult<>(code, msg, null); + } +} diff --git a/common/src/main/java/com/imitate/common/bean/BeanFactory.java b/common/src/main/java/com/imitate/common/bean/BeanFactory.java new file mode 100644 index 0000000..3d93707 --- /dev/null +++ b/common/src/main/java/com/imitate/common/bean/BeanFactory.java @@ -0,0 +1,44 @@ + +/** + * 文件名 : BeanFactory.java + * 版权 : <版权/公司名> + * 描述 : <描述> + * @author liliy + * 版本 : <版本> + * 修改时间: 2017年6月13日 + * 修改内容: <修改内容> + */ +package com.imitate.common.bean; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * <一句话功能简述> <功能详细描述> + * + * @author liliy + * @version [版本号,2017年6月13日] + * @see [相关类/方法] + * @since [产品/模块版本] + */ +@Component +public class BeanFactory implements ApplicationContextAware { + private static ApplicationContext ctx = null; + + public static Object getObejct(String name) { + return ctx.getBean(name); + } + + public static T getObejct(Class requiredType) { + return ctx.getBean(requiredType); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + BeanFactory.ctx = applicationContext; + + } + +} diff --git a/common/src/main/java/com/imitate/common/bean/BridgePage.java b/common/src/main/java/com/imitate/common/bean/BridgePage.java new file mode 100644 index 0000000..695f946 --- /dev/null +++ b/common/src/main/java/com/imitate/common/bean/BridgePage.java @@ -0,0 +1,53 @@ +package com.imitate.common.bean; + +import com.github.pagehelper.Page; + +import java.util.List; + +public class BridgePage { + + private List data; + private Integer pageNum; + private Integer pageSize; + private Long total; + + public BridgePage(Page page) { + this.data = page.getResult(); + this.pageNum = page.getPageNum(); + this.pageSize = page.getPageSize(); + this.total = page.getTotal(); + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public Integer getPageNum() { + return pageNum; + } + + public void setPageNum(Integer pageNum) { + this.pageNum = pageNum; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } + +} diff --git a/common/src/main/java/com/imitate/common/bean/ShellResult.java b/common/src/main/java/com/imitate/common/bean/ShellResult.java new file mode 100644 index 0000000..69ee0d8 --- /dev/null +++ b/common/src/main/java/com/imitate/common/bean/ShellResult.java @@ -0,0 +1,48 @@ +package com.imitate.common.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * shell执行结果 + * + * @author 威少 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ShellResult { + /** + * 退出码 + */ + private Integer exitStatus; + /** + * 实际输出 + */ + private String out; + + public enum ExitStatus { + /** + * 成功 + */ + SUCCESS(0), + /** + * 超时 + */ + TIMEOUT(124), + /** + * 默认失败 + */ + FAIL(-1); + + private int code; + ExitStatus(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + } +} diff --git a/common/src/main/java/com/imitate/common/config/DateConfig.java b/common/src/main/java/com/imitate/common/config/DateConfig.java new file mode 100644 index 0000000..1caef2a --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/DateConfig.java @@ -0,0 +1,141 @@ +package com.imitate.common.config; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +@Configuration +public class DateConfig { + + /** 默认日期时间格式 */ + public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + /** 默认日期格式 */ + public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; + /** 默认时间格式 */ + public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; + + /** + * LocalDate转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localDateConverter() { + return new Converter() { + @Override + public LocalDate convert(String source) { + return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)); + } + }; + } + + /** + * LocalDateTime转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localDateTimeConverter() { + return new Converter() { + @Override + public LocalDateTime convert(String source) { + return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)); + } + }; + } + + /** + * LocalTime转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter localTimeConverter() { + return new Converter() { + @Override + public LocalTime convert(String source) { + return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)); + } + }; + } + + /** + * Date转换器,用于转换RequestParam和PathVariable参数 + */ + @Bean + public Converter dateConverter() { + return new Converter() { + @Override + public Date convert(String source) { + SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT); + try { + return format.parse(source); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + }; + } + + + /** + * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json + */ + @Bean("objectMapper") + public ObjectMapper objectMapper(){ + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + + //LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); + javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))); + javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); + javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))); + javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))); + javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); + + + //Date序列化和反序列化 + javaTimeModule.addSerializer(Date.class, new JsonSerializer() { + @Override + public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT); + String formattedDate = formatter.format(date); + jsonGenerator.writeString(formattedDate); + } + }); + javaTimeModule.addDeserializer(Date.class, new JsonDeserializer() { + @Override + public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT); + String date = jsonParser.getText(); + try { + return format.parse(date); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + }); + + objectMapper.registerModule(javaTimeModule); + return objectMapper; + } + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/config/InitListener.java b/common/src/main/java/com/imitate/common/config/InitListener.java new file mode 100644 index 0000000..f35ce6b --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/InitListener.java @@ -0,0 +1,36 @@ +package com.imitate.common.config; + +import com.imitate.common.util.RedisPool; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import redis.clients.jedis.Jedis; + +@Component +public class InitListener implements ApplicationListener { + private static final Logger log = LoggerFactory.getLogger(InitListener.class); + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + String active = System.getProperty("spring.profiles.active"); + // 本地不初始化这些信息 + Jedis jedis = null; + try { + if (!StringUtils.equals(active, "local")) { + // 初始化redis + jedis = RedisPool.getJedis(); + } + } catch (Exception e) { + log.error("项目启动失败", e); + if ("jedisPool初始化错误".equals(e.getMessage())) { + Runtime.getRuntime().exit(-1); + } + } finally { + RedisPool.returnResource(jedis); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/config/JdbcConfig.java b/common/src/main/java/com/imitate/common/config/JdbcConfig.java new file mode 100644 index 0000000..4e994b3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/JdbcConfig.java @@ -0,0 +1,35 @@ +package com.imitate.common.config; + + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +/** + * @author yanchao + */ +@Component +public class JdbcConfig { + @Autowired + private Environment environment; + + private static final String HOST = "com.mysql.cj.jdbc.Driver"; + private static final String URL = "jdbc:mysql://rm-bp1ht3504joktie83.mysql.rds.aliyuncs.com:3306/educoderweb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false"; + private static final String USERNAME = "readonly"; + private static final String PASSWORD = "readonly_20210901"; + + + @Bean + public JdbcTemplate jdbcTemplate(){ + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName(HOST); + dataSource.setJdbcUrl(URL); + dataSource.setUsername(USERNAME); + dataSource.setPassword(PASSWORD); + return new JdbcTemplate(dataSource); + } + +} diff --git a/common/src/main/java/com/imitate/common/config/MybatisConfig.java b/common/src/main/java/com/imitate/common/config/MybatisConfig.java new file mode 100644 index 0000000..7421afe --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/MybatisConfig.java @@ -0,0 +1,9 @@ +package com.imitate.common.config; + +import org.springframework.context.annotation.Configuration; +import tk.mybatis.spring.annotation.MapperScan; + +@Configuration +public class MybatisConfig { + +} diff --git a/common/src/main/java/com/imitate/common/config/RedisListenerConfig.java b/common/src/main/java/com/imitate/common/config/RedisListenerConfig.java new file mode 100644 index 0000000..5ee9f45 --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/RedisListenerConfig.java @@ -0,0 +1,21 @@ +package com.imitate.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + + +@Order(1) +@Configuration +public class RedisListenerConfig { + + @Bean + RedisMessageListenerContainer listenerContainer(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/config/UncaughtBridgeExceptionHandler.java b/common/src/main/java/com/imitate/common/config/UncaughtBridgeExceptionHandler.java new file mode 100644 index 0000000..6254a08 --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/UncaughtBridgeExceptionHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017-2018 Educoder Group. + */ +package com.imitate.common.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * 未经捕获的异常处理 + * @author weishao + */ +@Service +public class UncaughtBridgeExceptionHandler implements Thread.UncaughtExceptionHandler { + + private static Logger logger = LoggerFactory.getLogger(UncaughtBridgeExceptionHandler.class); + + @Override + public void uncaughtException(Thread t, Throwable e) { + // 此处只处理善后异常,其它已经在善后处理中解决 + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/config/WebMvcConfig.java b/common/src/main/java/com/imitate/common/config/WebMvcConfig.java new file mode 100644 index 0000000..c4e084b --- /dev/null +++ b/common/src/main/java/com/imitate/common/config/WebMvcConfig.java @@ -0,0 +1,63 @@ +package com.imitate.common.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.imitate.common.interceptor.SignInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +import java.util.List; + + +/** + * + * @author yanchao + */ +@Configuration +@Order(1) +@Slf4j +public class WebMvcConfig extends WebMvcConfigurationSupport { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + //签名拦截器 + registry.addInterceptor(getHandlerInterceptor()); + } + + @Override + public void addArgumentResolvers(List resolvers) { + log.debug("【配置argumentResolver】ok"); + } + + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedHeaders("Content-Type", "x-requested-with", "X-Custom-Header") + .allowedOriginPatterns("*") + .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") + .allowCredentials(true) + .maxAge(3600); + } + + @Bean + public HandlerInterceptor getHandlerInterceptor() { + return new SignInterceptor(); + } + + + + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/constant/ApiResultCsts.java b/common/src/main/java/com/imitate/common/constant/ApiResultCsts.java new file mode 100644 index 0000000..7c11aee --- /dev/null +++ b/common/src/main/java/com/imitate/common/constant/ApiResultCsts.java @@ -0,0 +1,7 @@ +package com.imitate.common.constant; + +public interface ApiResultCsts { + int CODE_SUCCESS = 0; + int CODE_FAIL = -1; + +} diff --git a/common/src/main/java/com/imitate/common/constant/BuildResultCsts.java b/common/src/main/java/com/imitate/common/constant/BuildResultCsts.java new file mode 100644 index 0000000..45e98b3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/constant/BuildResultCsts.java @@ -0,0 +1,76 @@ +package com.imitate.common.constant; + +public interface BuildResultCsts { + + /** + * 完整结果正则。 + * + */ + // \{ 匹配 { , [\s]* 匹配0-多个空白字符, "compileResult"[\s]*:匹配"compileResult": + // [\s\S]*匹配0-多个任意字符, [\s\S]*"out"[\s]*:匹配"out": , [^}]*匹配除}之外的所有字符 + String FULL_RESULT_REG = "\\{[\\s]*\"compileResult\"[\\s]*:[\\s\\S]*\"out\"[\\s]*:[^}]*}"; + + String TIMEOUT_RES_USAGE = "\\{[\\s]*\"exitStatus\"[\\s]*:[\\s\\S]*\"evaCpuUsage\"[\\s]*:[\\s\\S]*\"nodeLoadAvg\"[\\s]*:[^}]*}"; + + String SYS_BUSY_TIP = "系统繁忙,请稍后重试"; + String SYS_BUSY_BASE64 = "57O757uf57mB5b-Z77yM6K-356iN5ZCO6YeN6K-V"; + + String EVA_UNDEFINED_ERROR_TIP = "程序执行失败导致评测提前终止,请稍后重试或联系系统管理员。"; + String EVA_UNDEFINED_ERROR_BASE64 = "56iL5bqP5omn6KGM5aSx6LSl5a-86Ie06K-E5rWL5o-Q5YmN57uI5q2i77yM6K-356iN5ZCO6YeN6K-V5oiW6IGU57O757O757uf566h55CG5ZGY44CC"; + + String EVA_UNPUBLISHED_SCRIPT_ERROR_TIP = "评测脚本设置异常,建议您在实训的配置页面重新选择或修改评测脚本。"; + String EVA_UNPUBLISHED_SCRIPT_ERROR_BASE64 = "6K-E5rWL6ISa5pys6K6-572u5byC5bi477yM5bu66K6u5oKo5Zyo5a6e6K6t55qE6YWN572u6aG16Z2i6YeN5paw6YCJ5oup5oiW5L-u5pS56K-E5rWL6ISa5pys44CC"; + + String EVA_PUBLISHED_SCRIPT_ERROR_TIP = "评测脚本设置异常,请联系老师在实训的配置页面重新选择或修改评测脚本。"; + String EVA_PUBLISHED_SCRIPT_ERROR_BASE64 = "6K-E5rWL6ISa5pys6K6-572u5byC5bi477yM6K-36IGU57O76ICB5biI5Zyo5a6e6K6t55qE6YWN572u6aG16Z2i6YeN5paw6YCJ5oup5oiW5L-u5pS56K-E5rWL6ISa5pys44CC"; + +// String EVA_RETRY_ERROR_TIP = "本实训有严格的资源限制,当前账号对资源占有级别较低,请10秒后重试。"; +// String EVA_RETRY_ERROR_BASE64 = "5pys5a6e6K6t5pyJ5Lil5qC855qE6LWE5rqQ6ZmQ5Yi277yM5b2T5YmN6LSm5Y-35a-56LWE5rqQ5Y2g5pyJ57qn5Yir6L6D5L2O77yM6K-3MTDnp5LlkI7ph43or5XjgII"; + + String EVA_RETRY_ERROR_TIP = "本次评测网络延迟较高,资源无法正常加载,请10s后重试。"; + String EVA_RETRY_ERROR_BASE64 = "5pys5qyh6K-E5rWL572R57uc5bu26L-f6L6D6auY77yM6LWE5rqQ5peg5rOV5q2j5bi45Yqg6L2977yM6K-3MTBz5ZCO6YeN6K-V44CC"; + + String EVA_WINDOWS_ERROR_TIP = "实验环境存在问题或已被回收,请保存数据再重置实训重新评测。"; + + String EVA_VIRTUAL_NOT_READY_TIP = "环境尚未初始化完成,请稍后重试。"; + + String POD_HAVE_BEEN_CLEANED_TIP = "当前资源因未持续使用正在回收中,稍后重试即可。"; + String POD_HAVE_BEEN_CLEANED_BASE64 = "5b2T5YmN6LWE5rqQ5Zug5pyq5oyB57ut5L2_55So5q2j5Zyo5Zue5pS25Lit77yM56iN5ZCO6YeN6K-V5Y2z5Y-v44CC"; + + String REDO_EVALUATING_FAIL_TIP = "当前账号两次评测间歇时间较短,请稍后重试!"; + String REDO_EVALUATING_FAIL_BASE64 = "5b2T5YmN6LSm5Y-35Lik5qyh6K-E5rWL6Ze05q2H5pe26Ze06L6D55-t77yM6K-356iN5ZCO6YeN6K-V77yB"; + + String EVA_OVER_MEMORY_LIMIT_TIP = "当前运行程序超过系统最大空间限制,请检查程序是否存在死循环或内存泄漏等问题!"; + String EVA_OVER_MEMORY_LIMIT_BASE64 = "5b2T5YmN6L+Q6KGM56iL5bqP6LaF6L+H57O757uf5pyA5aSn5YaF5a2Y6ZmQ5Yi277yM6K+35qOA5p+l56iL5bqP5piv5ZCm5a2Y5Zyo5q275b6q546v5oiW5YaF5a2Y5rOE5ryP562J6Zeu6aKY77yB"; + String EVA_OVER_MEMORY_LIMIT_BASE64_REENCODE = "5b2T5YmN6L-Q6KGM56iL5bqP6LaF6L-H57O757uf5pyA5aSn5YaF5a2Y6ZmQ5Yi277yM56iL5bqP5Y-v6IO95a2Y5Zyo5q275b6q546v5oiW6ICF5YaF5a2Y5rOE5ryP562J6Zeu6aKY77yBDQo"; + + String PULL_IMAGE_FAIL = "当前实验环境正在更新中,请稍后重试或联系系统管理员!"; + String CLONE_CODE_FAIL = "当前网络较差,代码下载超时,请稍后重试!"; + String CLONE_CODE_FAIL_BASE64_REENCODE = "5b2T5YmN572R57uc6L6D5beu77yM5Luj56CB5LiL6L296LaF5pe277yM6K-356iN5ZCO6YeN6K-V77yB"; + + String RES_SCALE_TIP = "当前实验使用的用户较多,系统正在智能化为您调度更优质的资源,预计一分钟内完成,请稍后重试!"; + String RES_SCALE_BASE64 = "5b2T5YmN5a6e6aqM5L2_55So55qE55So5oi36L6D5aSa77yM57O757uf5q2j5Zyo5pm66IO95YyW5Li65oKo6LCD5bqm5pu05LyY6LSo55qE6LWE5rqQ77yM6aKE6K6h5LiA5YiG6ZKf5YaF5a6M5oiQ77yM6K-356iN5ZCO6YeN6K-V77yB"; + + String OUTPUT_TOO_LONG_ERROR = "cannot allocate"; + String INPUT_TOO_LONG_ERROR = "argument list too long"; + String OUTPUT_TOO_LONG_TIP = "评测输出结果过长,请检查代码逻辑"; + String INPUT_TOO_LONG_TIP = "评测测试用例过长,请采用文件测试用例形式"; + + String COMPILE_SUCCESS_TIP = "compile successfully"; + String COMPILE_SUCCESS_BASE64 = "Y29tcGlsZSBzdWNjZXNzZnVsbHk"; + + String DOWNLOAD_STATUS = "downloadStatus"; + String DOWNLOAD_STATUS_SUCCESS = "1"; + String DOWNLOAD_STATUS_FAIL = "0"; + + String CREATE_POD_STATUS = "createPodStatus"; + String CREATE_POD_STATUS_SUCCESS = "1"; + String CREATE_POD_STATUS_FAIL = "0"; + + String COMPILE_SUCCESS_YES = "1"; + String COMPILE_SUCCESS_NO = "0"; + + String TEXT_MSG_SERVICE_START = "服务启动中..."; + String TEXT_MSG_SERVICE_SUCCESS = "服务启动完成"; + String TEXT_MSG_REDO_EVA = "重复评测中..."; +} diff --git a/common/src/main/java/com/imitate/common/constant/TpCsts.java b/common/src/main/java/com/imitate/common/constant/TpCsts.java new file mode 100644 index 0000000..1234e48 --- /dev/null +++ b/common/src/main/java/com/imitate/common/constant/TpCsts.java @@ -0,0 +1,217 @@ +package com.imitate.common.constant; + +public interface TpCsts { + String TPI_ID = "tpiID"; + /** + * 类型 + */ + String TYPE_EVALUATE = "evaluate"; + String TYPE_EVASSH = "evassh"; + String TYPE_WEBSSH = "webssh"; + String TYPE_VNC = "vnc"; + String TYPE_JUPYTER = "jupyter"; + String TYPE_VSCODE = "vscode"; + String TYPE_WEB = "web"; + + int POD_TYPE_EVALUATE = 0; + int POD_TYPE_WEBSSH = 1; + int POD_TYPE_EVASSH = 2; + int POD_TYPE_VNC = 3; + int POD_TYPE_JUPYTER = 4; + int POD_TYPE_VSCODE = 5; + int POD_TYPE_WEB = 6; + + /** + * 全部可评测实训clone到tpiWorkspace, 都命名为此 + */ + String TP_UNIFY_REPO_NAME = "myshixun"; + + + String TP_RASPBERRY_REPO_SCRIPT = "raspberry"; + + String TP_USERFILES_NAME = "userfiles"; + + /** + * pod 分配对node的要求 + */ + String NODE_REQUIRE = "nodeRequire"; + + String NODE_TYPE = "type"; + String NODE_TYPE_OTHERS = "others"; + String NODE_TYPE_GPU = "Python-GPU"; + + /** + * shenlong节点 + */ + String SHENLONG_NODE_LABEL_KEY = "shenlong"; + String SHENLONG_NODE_LABEL_VALUE = "true"; + + /** + * GPU节点 + */ + String GPU_NODE_LABEL_KEY = "GPU"; + String GPU_NODE_LABEL_VALUE = "true"; + + /** + * oj pod 和 node + */ + String OJ_LABEL_KEY = "oj"; + String OJ_LABEL_VALUE = "true"; + + String OJ_CAPACITY_NODE_LABEL_KEY = "capacity"; + + /** + * OJ 平台类型 + */ + String PLATFORM = "platform"; + + /** + * tpi评测需要用到的平台资源,在pod中的挂载路径 + */ + String TPI_PLATFORM_MOUNT_PATH = "/data/platform/eva"; + + + String VNC_POD_DEV_SHM_PATH = "/dev/shm"; + + /** + * tpi保护空间的挂载路径,存放评测执行脚本等 + */ + String TPI_PROTECT_SPACE_MOUNT_PATH = "/data/protectspace"; + /** + * tpi工作空间的挂载路径 + */ + String TPI_WORKSPACE_MOUNT_PATH = "/data/workspace"; + + /** + * 评测分步输出消息匹配格式 + */ + String EVA_STEP_OUT_MSG_PREFIX = "{\"step\":"; + String EVA_STEP_OUT_MSG_SUFFIX = "}"; + + + /** + * 超时编码:不确定 + */ + int TIMEOUT_CODE_UNSURE = 1; + /** + * 超时编码:死循环 + */ + int TIMEOUT_CODE_DEAD_LOOP = 2; + /** + * 超时编码:阻塞。(1)读取输入阻塞,(2)网络编程端口阻塞,(3)多线程阻塞。 + */ + int TIMEOUT_CODE_BLOCKING = 3; + /** + * 超时编码:节点繁忙 + */ + int TIMEOUT_CODE_NODE_BUSY = 4; + + /** + * 主题:pod 调度 + */ + String TOPIC_POD_SCHEDULE = "pod_schedule"; + /** + * 主题:评测 + */ + String TOPIC_EVALUATING = "evaluating"; + + String POD_SCHEDULE_CLUSTER_REDIS_KEY = "pod_schedule_cluster_"; + + long POD_SCHEDULE_CLUSTER_REDIS_EXPIRE_TIME = 5000; + + String STATEFUL_POD_CLUSTER_RECORD_REDIS_KEY = "stateful_pod_schedule_"; + + long STATEFUL_POD_CLUSTER_RECORD_EXPIRE_TIME = 2 * 24 * 60 * 60 * 1000; // 2天 + + /** + * 评测开始存入redis,评测结束则删除。存在key,表明评测正在进行,value为true表示为重复评测。 + */ + String REDO_EVALUATING_REDIS_KEY = "redo_evaluating_"; + + String REDO_EVALUATING_REDIS_VALUE_TRUE = "true"; + + String REDO_EVALUATING_REDIS_VALUE_FALSE = "false"; + + String RESET_LOCAL_REDIS_KEY = "reset_local_"; + + String CREATE_IMAGE_MAP = "create_image"; + + String CREATE_IMAGE_NAME_REDIS_KEY = "create_image_name_"; + + Long CREATE_IMAGE_NAME_REDIS_EXPIRE_TIME = 30 * 24 * 60 * 60 * 1000L; // 30天 + + String CREATE_IMAGE_TPI_ID_REDIS_KEY = "create_image_tpi_id_"; + + long CREATE_IMAGE_TPI_ID_REDIS_EXPIRE_TIME = 60 * 1000L; // 1分钟 + + long NODE_IMAGE_STATUS_EXPIRE_TIME = 30 * 60 * 1000L; // 30分钟 + + String K8S_NODE_STATUS_READY = "Ready"; + + String K8S_NODE_STATUS_NOT_READY = "NotReady"; + + String K8S_NODE_STATUS_LABEL_VALUE = "status"; + + String K8S_ELASTIC_NODE_LABEL_KEY = "tx"; + + String K8S_ELASTIC_NODE_LABEL_VALUE = "normal"; + + String K8S_NEW_CAPACITY_NODE_LABEL_KEY = "newCapacity"; + + String K8S_NEW_CAPACITY_NODE_LABEL_VALUE = "true"; + + String K8S_NEW_CAPACITY_NODE_REDIS_KEY = "k8s_new_capacity_node_key"; + + String K8S_NODE_PRE_REDUCE_LABEL_KEY = "pre_reduce"; + + String K8S_NODE_PRE_REDUCE_LABEL_VALUE = "true"; + + String K8S_PRE_REDUCE_NODE_REDIS_KEY = "k8s_pre_reduce_node_key"; + + String CLOUD_HOST_TYPE_WINDOWS = "windows"; + + String PROXMOX_HOST_TYPE_VIRTUAL = "virtual"; + + String CLOUD_HOST_TYPE_LINUX = "linux"; + + Integer NODE_CPU_USAGE_UNKNOWN = -1; + + String JUPYTER_TYPE_LAB = "lab"; + String JUPYTER_TYPE_NOTEBOOK = "notebook"; + + String IGNORED = "ignored"; + + /** + * 用户实时运行输出位置 + */ + String USER_OUTPUT_FILE = "user.out"; + + /** + * 用户case输出分隔符 + */ + String CASE_OUTPUT_SEPARATOR = "\\x1b\\x09\\x1d"; + + /** + * 本地集群 + */ + String LOCAL_CLUSTER = "local"; + + /** + * 默认工作空间 + */ + String DEFAULT_NAMESPACE = "default"; + + /** + * 挂载点名称 + */ + String WORKSPACE = "workspace"; + String TEST_CASE_DIR = "test-case-dir"; + String TEST_CASE_ACTUAL_OUT_DIR = "test-case-actual-out-dir"; + String PROTECT_DIR = "protect"; + + /** + * 仅运行结果前缀 + */ + String RUN_ONLY_RESULT_KEY_PREFIX = "runOnlyResult"; + +} diff --git a/common/src/main/java/com/imitate/common/enums/CommonStateEnum.java b/common/src/main/java/com/imitate/common/enums/CommonStateEnum.java new file mode 100644 index 0000000..656ed4f --- /dev/null +++ b/common/src/main/java/com/imitate/common/enums/CommonStateEnum.java @@ -0,0 +1,53 @@ +package com.imitate.common.enums; + +/** + * 通用状态枚举 0为否定含义,1为肯定含义 + * @author 威少 + */ +public enum CommonStateEnum { + /** + * 否定含义 + */ + FALSE(0, false), + /** + * 肯定含义 + */ + TRUE(1, true); + + int value; + + boolean booleanValue; + + CommonStateEnum(int value, boolean booleanValue) { + this.value = value; + this.booleanValue = booleanValue; + } + + + public int getValue() { + return value; + } + + public boolean getBooleanValue() { + return booleanValue; + } + + /** + * 从bool值转换 + * @param value bool值 + * @return 肯定为1,否定为0 + */ + public static CommonStateEnum fromBoolean(Boolean value) { + return value == null ? FALSE : value ? TRUE : FALSE; + } + + /** + * 从整型值转换 + * @param value 整型值 + * @return 肯定为1,否定为0 + */ + public static CommonStateEnum fromInteger(Integer value) { + return value == null || value == FALSE.getValue() ? FALSE : TRUE; + } + +} diff --git a/common/src/main/java/com/imitate/common/enums/ErrorCodeEnum.java b/common/src/main/java/com/imitate/common/enums/ErrorCodeEnum.java new file mode 100644 index 0000000..e10db0e --- /dev/null +++ b/common/src/main/java/com/imitate/common/enums/ErrorCodeEnum.java @@ -0,0 +1,60 @@ +package com.imitate.common.enums; + +import java.util.Arrays; + + +/** + * 全局错误枚举 + * @author yanchao + */ +public enum ErrorCodeEnum { + SUCCESS("000000","成功"), + INVALID_ARG_EXCEPTION("000001","参数验证异常"), + BIND_EXCEPTION("000007","参数绑定异常"), + MVC_BIND_EXCEPTION("000014","请求参数绑定异常"), + METHOD_NOT_ALLOWED_EXCEPTION("000008","请求方式异常"), + EXCEPTION("999999","系统异常"), + DATAFLOW_EXCEPTION("000010","默认数据异常"), + BUSINESS_EXCEPTION("000011","默认业务异常"), + LOGIN_EXPIRE_TIME("000013","登录已过期,请重新登录"), + USER_LOGIN_DISABLE("000014","账号已经锁定,请联系管理员"), + NO_AUTH("000012","未认证登录状态"), + AFTERMATH_EXP("000013","评测线程出错,善后处理发生异常"), + GIT_FAIL("000014","获取git凭证失败: "), + GIT_CREDENTIAL_FAIL("000015","设置git凭证失败:"), + + + CLONE_FAIL("100001","克隆失败"), + EVALUATION_SHELL_FAIL("100002","生成评测脚本失败"), + WRITE_FILE_CODE_FAIL("100003","写代码文件失败"), + UPDATE_VERSION_REPOSITORY_FAIL("100004","更新版本库失败"), + SYNC_CLUSTER_VERSION_REPOSITORY_FAIL("100005","远程集群版本库同步失败"), + PUSH_FAIL("100006","push版本库失败"), + JUPYTER_ADD_OR_COMMIT_FAIL("100007","Jupyter添加提交失败"), + VERSION_REPOSITORY_NOT_EXIST("10000","主机名不存在"); + + + String value; + String description; + + ErrorCodeEnum(String value, String description) { + this.value = value; + this.description = description; + } + + public String getValue() { + return value; + } + + public String getDescription() { + return description; + } + + public static ErrorCodeEnum getByDescription(String description) { + return Arrays.stream(values()).filter(errorCodeEnum -> errorCodeEnum.getDescription().equals(description)).findFirst().orElse(null); + } + +} + + + diff --git a/common/src/main/java/com/imitate/common/exception/BusinessException.java b/common/src/main/java/com/imitate/common/exception/BusinessException.java new file mode 100644 index 0000000..0ef2513 --- /dev/null +++ b/common/src/main/java/com/imitate/common/exception/BusinessException.java @@ -0,0 +1,36 @@ +package com.imitate.common.exception; + +import com.imitate.common.enums.ErrorCodeEnum; + +/** + * 业务异常 + * @author yanchao + */ +public class BusinessException extends RuntimeException{ + /** + * 错误码 + */ + private String errCode = ErrorCodeEnum.DATAFLOW_EXCEPTION.getValue(); + + public BusinessException(ErrorCodeEnum errorCodeEnum){ + super(errorCodeEnum.getDescription()); + setErrCode(errorCodeEnum.getValue()); + } + + + public BusinessException(String code,String msg){ + super(msg); + setErrCode(code); + } + + + + public void setErrCode(String errCode) { + this.errCode = errCode; + } + + public String getErrCode() { + return errCode; + } +} + diff --git a/common/src/main/java/com/imitate/common/interceptor/HttpServletRequestFilter.java b/common/src/main/java/com/imitate/common/interceptor/HttpServletRequestFilter.java new file mode 100644 index 0000000..2f562d3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/interceptor/HttpServletRequestFilter.java @@ -0,0 +1,117 @@ +package com.imitate.common.interceptor; + + +import com.imitate.common.util.HttpContextUtil; +import org.apache.commons.io.IOUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.StandardCharsets; + +/*** + * HttpServletRequest 过滤器 + * 解决: request.getInputStream()只能读取一次的问题 + * 目标: 流可重复读 + * @author yanchao + */ +@Component +@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/") +@Order(10000) +public class HttpServletRequestFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + ServletRequest requestWrapper = null; + if(servletRequest instanceof HttpServletRequest) { + requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); + } + //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中 + // 在chain.doFiler方法中传递新的request对象 + if(null == requestWrapper) { + filterChain.doFilter(servletRequest, servletResponse); + } else { + filterChain.doFilter(requestWrapper, servletResponse); + } + } + + @Override + public void destroy() { + } + + /*** + * HttpServletRequest 包装器 + * 解决: request.getInputStream()只能读取一次的问题 + * 目标: 流可重复读 + */ + public static class RequestWrapper extends HttpServletRequestWrapper { + //参数字节数组 + private byte[] requestBody; + //Http请求对象 + private HttpServletRequest request; + + public RequestWrapper(HttpServletRequest request) throws IOException { + super(request); + this.request = request; + } + + /** + * @return + * @throws IOException + */ + @Override + public ServletInputStream getInputStream() throws IOException { + /** + * 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中 + * 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题 + */ + if (null == this.requestBody) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + IOUtils.copy(request.getInputStream(), baos); + this.requestBody = baos.toByteArray(); + } + + final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); + return new ServletInputStream() { + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener listener) { + + } + + @Override + public int read() { + return bais.read(); + } + }; + } + + public byte[] getRequestBody() { + return requestBody; + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + } + + + +} diff --git a/common/src/main/java/com/imitate/common/interceptor/SignInterceptor.java b/common/src/main/java/com/imitate/common/interceptor/SignInterceptor.java new file mode 100644 index 0000000..d12d67e --- /dev/null +++ b/common/src/main/java/com/imitate/common/interceptor/SignInterceptor.java @@ -0,0 +1,142 @@ +package com.imitate.common.interceptor; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import com.imitate.common.annotation.PublicUrl; +import com.imitate.common.util.SignUtil; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.Map; + + +/** + * 签名认证拦截器 + * @author 悟空 + */ +public class SignInterceptor implements HandlerInterceptor { + + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, Object handler) throws Exception { + if(handler instanceof HandlerMethod){ + HandlerMethod handlerMethod = (HandlerMethod) handler; + + ValidateResponse validateResponse = new ValidateResponse(true); + PublicUrl publicUrl = handlerMethod.getMethodAnnotation(PublicUrl.class); + if (null != publicUrl) { + if (publicUrl.signValidate()) { + + BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8")); + StringBuilder responseStrBuilder = new StringBuilder(); + String inputStr; + while ((inputStr = streamReader.readLine()) != null) { + responseStrBuilder.append(inputStr); + } + Map params = JSON.parseObject(responseStrBuilder.toString(), Map.class); + + if(params != null){ + validateResponse = paramSignValidate(params, response); + }else{ + validateResponse = paramSignValidate(request, response); + } + + } + } + /*if (!validateResponse.isValidate()) { + JSONObject result = new JSONObject(); + result.put("code","-1"); + result.put("msg","签名认证失败"); + returnJson(response,result.toJSONString()); + return false; + }*/ + } + return true; + } + + + /** + * 签名校验 + * + * @param request + * @param response + * @return + */ + private ValidateResponse paramSignValidate(HttpServletRequest request, HttpServletResponse response) { + String sign = request.getParameter("sign"); + Map map = request.getParameterMap(); + // 将参数按照一定规则获取到sign和前端传过来的sign进行比较,规则自定义需要和前端一致 + String sign1 = SignUtil.signMap(map); + if (ObjectUtil.isEmpty(sign) || !sign.equals(sign1)) { + return new ValidateResponse(false); + } + return new ValidateResponse(true); + } + + /** + * 签名校验 + * + * @param + * @param response + * @return + */ + private ValidateResponse paramSignValidate(Map params, HttpServletResponse response) { + String sign = params.get("sign"); + // 将参数按照一定规则获取到sign和前端传过来的sign进行比较,规则自定义需要和前端一致 + String sign1 = SignUtil.signMap(null, params); + if (ObjectUtil.isEmpty(sign) || !sign.equals(sign1)) { + return new ValidateResponse(false); + } + return new ValidateResponse(true); + } + + + /** + * 校验返回对象 + */ + private static class ValidateResponse { + private boolean validate; + public ValidateResponse(boolean validate) { + this.validate = validate; + } + public boolean isValidate() { + return validate; + } + } + + + + + /** + * 认证失败返回json数据 + * @param response + * @param json + * @throws Exception + */ + @SuppressWarnings("unused") + private void returnJson(HttpServletResponse response, String json) throws Exception{ + PrintWriter writer = null; + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json; charset=utf-8"); + try { + writer = response.getWriter(); + writer.print(json); + } catch (IOException e) { + } finally { + if (writer != null) { + writer.close(); + } + } + } + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/BridgeContainer.java b/common/src/main/java/com/imitate/common/k8s/bean/BridgeContainer.java new file mode 100644 index 0000000..79363ff --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/BridgeContainer.java @@ -0,0 +1,73 @@ +package com.imitate.common.k8s.bean; + + + +public class BridgeContainer { + private boolean mainContainer; + + private String name; + + private String image; + + private Double cpuRequest; + private Double cpuLimit; + private Double memoryRequest; + private Double memoryLimit; + + public boolean isMainContainer() { + return mainContainer; + } + + public void setMainContainer(boolean mainContainer) { + this.mainContainer = mainContainer; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public Double getCpuRequest() { + return cpuRequest; + } + + public void setCpuRequest(Double cpuRequest) { + this.cpuRequest = cpuRequest; + } + + public Double getCpuLimit() { + return cpuLimit; + } + + public void setCpuLimit(Double cpuLimit) { + this.cpuLimit = cpuLimit; + } + + public Double getMemoryRequest() { + return memoryRequest; + } + + public void setMemoryRequest(Double memoryRequest) { + this.memoryRequest = memoryRequest; + } + + public Double getMemoryLimit() { + return memoryLimit; + } + + public void setMemoryLimit(Double memoryLimit) { + this.memoryLimit = memoryLimit; + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/BridgeNode.java b/common/src/main/java/com/imitate/common/k8s/bean/BridgeNode.java new file mode 100644 index 0000000..1ea3883 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/BridgeNode.java @@ -0,0 +1,47 @@ +package com.imitate.common.k8s.bean; + + +import java.time.LocalDateTime; + +public class BridgeNode { + + private String name; + private String ip; + + private LocalDateTime createTime; + + private Integer podNum; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public Integer getPodNum() { + return podNum; + } + + public void setPodNum(Integer podNum) { + this.podNum = podNum; + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/BuildResult.java b/common/src/main/java/com/imitate/common/k8s/bean/BuildResult.java new file mode 100644 index 0000000..1bbc684 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/BuildResult.java @@ -0,0 +1,231 @@ +package com.imitate.common.k8s.bean; + +import java.util.List; + +/** + * 评测结果 + * + * @author liliy + */ + +public class BuildResult { + private String buildID; + private String tpiID; + private String status; + private Integer timeoutCode; + private String outPut; + private List msg; + private String resubmit; + private String compileSuccess; + private String executeSuccess; + private String createPodStatus; + private String downloadStatus; + private String sec_key; + private String showServer; + private String extras; + + public boolean isSysBusy() { + return "57O757uf57mB5b-Z77yM6K-356iN5ZCO6YeN6K-V".equals(outPut); + } + + public boolean isTimeOut() { + String timeOutMsg = "5Luj56CB6K-E5rWL6LaF5pe277yB"; + for (ExecResultCase c : msg) { + if (c.getOutput().startsWith(timeOutMsg)) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + /** + * 步骤。输出分步输出时的步骤 + */ + private String step; + /** + * 输出存文本 + */ + private String textMsg; + + public String getSec_key() { + return sec_key; + } + + public void setSec_key(String sec_key) { + this.sec_key = sec_key; + } + + public String getBuildID() { + return buildID; + } + + public void setBuildID(String buildID) { + this.buildID = buildID; + } + + public String getTpiID() { + return tpiID; + } + + public void setTpiID(String tpiID) { + this.tpiID = tpiID; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getOutPut() { + return outPut; + } + + public void setOutPut(String outPut) { + this.outPut = outPut; + } + + public List getMsg() { + return msg; + } + + public void setMsg(List msg) { + this.msg = msg; + } + + public String getResubmit() { + return resubmit; + } + + public void setResubmit(String resubmit) { + this.resubmit = resubmit; + } + + public BuildResult(String buildID, String tpiID, String status, String outPut, List msg, String resubmit, + String sec_key, String showServer, String extras) { + this.buildID = buildID; + this.tpiID = tpiID; + this.status = status; + this.outPut = outPut; + this.msg = msg; + this.resubmit = resubmit; + this.sec_key = sec_key; + this.showServer = showServer; + this.extras = extras; + } + + public String getCompileSuccess() { + return compileSuccess; + } + + public void setCompileSuccess(String compileSuccess) { + this.compileSuccess = compileSuccess; + } + + public String getStep() { + return step; + } + + public void setStep(String step) { + this.step = step; + } + + public String getTextMsg() { + return textMsg; + } + + public void setTextMsg(String textMsg) { + this.textMsg = textMsg; + } + + public String getCreatePodStatus() { + return createPodStatus; + } + + public void setCreatePodStatus(String createPodStatus) { + this.createPodStatus = createPodStatus; + } + + public String getDownloadStatus() { + return downloadStatus; + } + + public void setDownloadStatus(String downloadStatus) { + this.downloadStatus = downloadStatus; + } + + public String getShowServer() { + return showServer; + } + + public void setShowServer(String showServer) { + this.showServer = showServer; + } + + public Integer getTimeoutCode() { + return timeoutCode; + } + + public void setTimeoutCode(Integer timeoutCode) { + this.timeoutCode = timeoutCode; + } + + public String getExtras() { + return extras; + } + + public void setExtras(String extras) { + this.extras = extras; + } + + public String getExecuteSuccess() { + return executeSuccess; + } + + public void setExecuteSuccess(String executeSuccess) { + this.executeSuccess = executeSuccess; + } + + public enum Status { + /** + * 评测通过 + */ + PASS("0"), + /** + * 评测不通过 + */ + FAIL("-1"), + /** + * oj 评测超时 + */ + RUN_TIMEOUT("2"), + /** + * 创建pod 超时 + */ + CREATE_POD_FAIL("3"), + /** + * 编译错误 + */ + COMPILE_FAIL("4"), + /** + * 执行错误,执行用户程序,进程返回-1。如数组越界 + */ + EXECUTE_ERROR("5"); + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + Status(String value) { + this.value = value; + } + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/ClusterInfo.java b/common/src/main/java/com/imitate/common/k8s/bean/ClusterInfo.java new file mode 100644 index 0000000..049e819 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/ClusterInfo.java @@ -0,0 +1,384 @@ +package com.imitate.common.k8s.bean; + + +import com.google.common.util.concurrent.AtomicDouble; +import com.imitate.common.k8s.constant.BridgeNodeCsts; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.pojo.ClusterConfig; +import io.fabric8.kubernetes.api.model.Node; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ClusterInfo { + /** + * 集群配置 + */ + private ClusterConfig clusterConfig; + + /** + * 是否自动扩容 + */ + private Boolean autoscale; + + /** + * 比重 + */ + private double weight; + + /** + * 集群节点 Map + */ + private Map nodeMap; + + /** + * 集群pod总数 + */ + private int podCount; + + /** + * 集群可分配cpu 总量 + */ + private double allocatableCpu; + + /** + * 集群可分配memory 总量,以M为单位 + */ + private double allocatableMemory; + + /** + * 固定节点数量 + */ + private int foreverNodeNum; + + /** + * 扩容节点数量 + */ + private int scaleNodeNum; + + /** + * 集群已分配的request cpu 总量 + */ + private double requestCpuSum; + + /** + * 集群已分配的request memory 总量 + */ + private double requestMemorySum; + + /** + * 集群已被预订的request cpu 总量。pod已经被分配到此集群,但还没有创建。 + */ + private AtomicDouble reserveRequestCpuSum = new AtomicDouble(0); + + /** + * 集群已被预订的request memory 总量。pod已经被分配到此集群,但还没有创建。 + */ + private AtomicDouble reserveRequestMemorySum = new AtomicDouble(0); + + /** + * 集群剩余可分配的request cpu 总量 + */ + private double surplusCpuSum; + + /** + * 集群剩余可分配的request memory 总量 + */ + private double surplusMemorySum; + + /** + * 集群固定节点可分配cpu 总量 + */ + private double foreverAllocatableCpuSum; + + /** + * 集群固定节点可分配memory 总量 + */ + private double foreverAllocatableMemorySum; + + /** + * 折算固定节点 request cpu比例。把扩容节点上的request cpu折算到固定节点 + */ + private double conversionForeverNodeRequestCpuRatio; + + /** + * 折算固定节点 request memory比例。把扩容节点上的request memory折算到固定节点 + */ + private double conversionForeverNodeRequestMemoryRatio; + + private double requestCpuRatio; + private double requestMemoryRatio; + + public void cal(String cluster, List nodes, List nodeResStats) { + Map nodeMap = new HashMap<>(); + for (Node node : nodes) { + nodeMap.put(node.getMetadata().getName(), node); + } + + double allocatableCpuCount = 0; + double allocatableMemoryCount = 0; + double foreverNodeAllocatableCpuCount = 0; + double foreverNodeAllocatableMemoryCount = 0; + int foreverNodeNum = 0; + int scaleNodeNum = 0; + for(Node node : nodes) { + String typeVal = K8sUtils.getLabel(node, BridgeNodeCsts.NODE_LABEL_TYPE); + boolean isEvaNode = BridgeNodeCsts.NODE_LABEL_TYPE_OTHERS.equals(typeVal) + || BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE.equals(typeVal); + if(!isEvaNode || K8sUtils.isOjNode(node)) { // 普通评测节点 + continue; + } + + double nodeAllocatableCpu = K8sUtils.getNodeAllocatableCpu(node); + allocatableCpuCount += nodeAllocatableCpu; + + double allocatableMemory = K8sUtils.getNodeAllocatableMemory(node); + allocatableMemoryCount += allocatableMemory; + + String txVal = K8sUtils.getLabel(node, "tx"); + if("normal".equals(txVal)) { // 扩容节点 + scaleNodeNum ++; + } else { + foreverNodeAllocatableCpuCount += nodeAllocatableCpu; + foreverNodeAllocatableMemoryCount += allocatableMemory; + foreverNodeNum ++; + } + } + + double requestCpuSum = 0; + double requestMemorySum = 0; + + int podCount = 0; + for (NodeResStat nodeResStat : nodeResStats) { + if (cluster.equals(nodeResStat.getCluster())) { + podCount += nodeResStat.getPodNum(); + requestCpuSum += nodeResStat.getCpuRequest(); + requestMemorySum += nodeResStat.getMemoryRequest(); + } + } + + double conversionForeverNodeRequestCpuRatio = requestCpuSum / foreverNodeAllocatableCpuCount * 100; + double conversionForeverNodeRequestMemoryRatio = requestMemorySum / foreverNodeAllocatableMemoryCount * 100; + double surplusCpuSum = allocatableCpuCount - requestCpuSum; + double surplusMemorySum = allocatableMemoryCount - requestMemorySum; + double requestCpuRatio = allocatableCpuCount == 0 ? 100 : (requestCpuSum / allocatableCpuCount * 100); + double requestMemoryRatio = allocatableMemoryCount == 0 ? 100 + : (requestMemorySum / allocatableMemoryCount * 100); + + setNodeMap(nodeMap); + setPodCount(podCount); + setAllocatableCpu(allocatableCpuCount); + setAllocatableMemory(allocatableMemoryCount); + setForeverNodeNum(foreverNodeNum); + setScaleNodeNum(scaleNodeNum); + setRequestCpuSum(requestCpuSum); + setRequestMemorySum(requestMemorySum); + setSurplusCpuSum(surplusCpuSum); + setSurplusMemorySum(surplusMemorySum); + setForeverAllocatableCpuSum(foreverNodeAllocatableCpuCount); + setForeverAllocatableMemorySum(foreverNodeAllocatableMemoryCount); + setConversionForeverNodeRequestCpuRatio(conversionForeverNodeRequestCpuRatio); + setConversionForeverNodeRequestMemoryRatio(conversionForeverNodeRequestMemoryRatio); + setRequestCpuRatio(requestCpuRatio); + setRequestMemoryRatio(requestMemoryRatio); + } + + + public ClusterConfig getClusterConfig() { + return clusterConfig; + } + + public void setClusterConfig(ClusterConfig clusterConfig) { + this.clusterConfig = clusterConfig; + } + + public Boolean getAutoscale() { + return autoscale; + } + + public void setAutoscale(Boolean autoscale) { + this.autoscale = autoscale; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public Map getNodeMap() { + return nodeMap; + } + + public void setNodeMap(Map nodeMap) { + this.nodeMap = nodeMap; + } + + public double getAllocatableCpu() { + return allocatableCpu; + } + + public void setAllocatableCpu(double allocatableCpu) { + this.allocatableCpu = allocatableCpu; + } + + public double getAllocatableMemory() { + return allocatableMemory; + } + + public void setAllocatableMemory(double allocatableMemory) { + this.allocatableMemory = allocatableMemory; + } + + public int getForeverNodeNum() { + return foreverNodeNum; + } + + public void setForeverNodeNum(int foreverNodeNum) { + this.foreverNodeNum = foreverNodeNum; + } + + public int getScaleNodeNum() { + return scaleNodeNum; + } + + public void setScaleNodeNum(int scaleNodeNum) { + this.scaleNodeNum = scaleNodeNum; + } + + public double getRequestCpuSum() { + return requestCpuSum; + } + + public void setRequestCpuSum(double requestCpuSum) { + this.requestCpuSum = requestCpuSum; + } + + public double getRequestMemorySum() { + return requestMemorySum; + } + + public void setRequestMemorySum(double requestMemorySum) { + this.requestMemorySum = requestMemorySum; + } + + public double getReserveRequestCpuSum() { + return reserveRequestCpuSum.doubleValue(); + } + + public void addReserveRequestCpu(double reserveRequestCpu) { + this.reserveRequestCpuSum.addAndGet(reserveRequestCpu); + } + + public double getReserveRequestMemorySum() { + return reserveRequestMemorySum.doubleValue(); + } + + public void addReserveRequestMemory(double reserveRequestMemory) { + this.reserveRequestMemorySum.addAndGet(reserveRequestMemory); + } + + public double getSurplusCpuSum() { + return surplusCpuSum; + } + + public void setSurplusCpuSum(double surplusCpuSum) { + this.surplusCpuSum = surplusCpuSum; + } + + public double getSurplusMemorySum() { + return surplusMemorySum; + } + + public void setSurplusMemorySum(double surplusMemorySum) { + this.surplusMemorySum = surplusMemorySum; + } + + public double getConversionForeverNodeRequestCpuRatio() { + return conversionForeverNodeRequestCpuRatio; + } + + public void setConversionForeverNodeRequestCpuRatio(double conversionForeverNodeRequestCpuRatio) { + this.conversionForeverNodeRequestCpuRatio = conversionForeverNodeRequestCpuRatio; + } + + public double getConversionForeverNodeRequestMemoryRatio() { + return conversionForeverNodeRequestMemoryRatio; + } + + public void setConversionForeverNodeRequestMemoryRatio(double conversionForeverNodeRequestMemoryRatio) { + this.conversionForeverNodeRequestMemoryRatio = conversionForeverNodeRequestMemoryRatio; + } + + public double getForeverAllocatableCpuSum() { + return foreverAllocatableCpuSum; + } + + public void setForeverAllocatableCpuSum(double foreverAllocatableCpuSum) { + this.foreverAllocatableCpuSum = foreverAllocatableCpuSum; + } + + public double getForeverAllocatableMemorySum() { + return foreverAllocatableMemorySum; + } + + public void setForeverAllocatableMemorySum(double foreverAllocatableMemorySum) { + this.foreverAllocatableMemorySum = foreverAllocatableMemorySum; + } + + public double getRequestCpuRatio() { + return requestCpuRatio; + } + + + public void setRequestCpuRatio(double requestCpuRatio) { + this.requestCpuRatio = requestCpuRatio; + } + + + public double getRequestMemoryRatio() { + return requestMemoryRatio; + } + + + public void setRequestMemoryRatio(double requestMemoryRatio) { + this.requestMemoryRatio = requestMemoryRatio; + } + + public int getPodCount() { + return podCount; + } + + public void setPodCount(int podCount) { + this.podCount = podCount; + } + + @Override + public String toString() { + return "ClusterInfo{" + + "clusterConfig=" + clusterConfig + + ", autoscale=" + autoscale + + ", weight=" + weight + + ", nodeMap=" + nodeMap.size() + + ", allocatableCpu=" + allocatableCpu + + ", allocatableMemory=" + allocatableMemory + + ", foreverNodeNum=" + foreverNodeNum + + ", scaleNodeNum=" + scaleNodeNum + + ", requestCpuSum=" + requestCpuSum + + ", requestMemorySum=" + requestMemorySum + + ", reserveRequestCpuSum=" + reserveRequestCpuSum + + ", reserveRequestMemorySum=" + reserveRequestMemorySum + + ", surplusCpuSum=" + surplusCpuSum + + ", surplusMemorySum=" + surplusMemorySum + + ", foreverAllocatableCpuSum=" + foreverAllocatableCpuSum + + ", foreverAllocatableMemorySum=" + foreverAllocatableMemorySum + + ", conversionForeverNodeRequestCpuRatio=" + conversionForeverNodeRequestCpuRatio + + ", conversionForeverNodeRequestMemoryRatio=" + conversionForeverNodeRequestMemoryRatio + + '}'; + } + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/ContainerCreateParams.java b/common/src/main/java/com/imitate/common/k8s/bean/ContainerCreateParams.java new file mode 100644 index 0000000..e8ca0c6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/ContainerCreateParams.java @@ -0,0 +1,143 @@ +package com.imitate.common.k8s.bean; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.Splitter; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +/** + * 创建容器的参数 + * + * @author 威少 + */ +@Data +@ToString +@Builder +public class ContainerCreateParams { + /** + * 超级权限 + */ + private boolean privileged = false; + + /** + * 镜像 + */ + private String image; + + /** + * 镜像名 + */ + private String imageName; + + /** + * 容器拉取策略 + */ + private String imagePullPolicy = "IfNotPresent"; + + /** + * 内存限制 + */ + private String memoryLimit; + + /** + * CPU限制 + */ + private String cpuLimit; + + /** + * 内存request + */ + private String memoryRequest; + + /** + * CPU request + */ + private String cpuRequest; + + /** + * 磁盘限制 + */ + private String diskLimit; + + /** + * 容器类型 + */ + private Type type; + + /** + * 启动时间限制 + */ + private Integer startTimeLimit; + + /** + * 容器挂载点信息 + */ + private List containerMountParams; + + /** + * 启动命令 + */ + private List command; + + /** + * prestop hook 执行的命令 + */ + private List prestopCommand; + + /** + * poststart hook 执行的指令 + */ + private List postStartCommand; + + /** + * 从json转换,格式形如 + * {"image":"python3-ssh:v1.0","cpuLimit":2.0,"cpuRequest":0.1,"memoryLimit":"2048M", + * "memoryRequest":"10M","resourceLimit":"10000K","startTime":15,"type":"main"} + */ + public static ContainerCreateParams fromJSON(JSONObject containers) { + ContainerCreateParams containerCreateParams = ContainerCreateParams.builder() + .image(containers.getString("image")) + .cpuLimit(containers.getString("cpuLimit")) + .memoryLimit(containers.getString("memoryLimit")) + .memoryRequest(containers.getString("memoryRequest")) + .cpuRequest(containers.getString("cpuRequest")) + .diskLimit(containers.getString("resourceLimit")) + .startTimeLimit(containers.getInteger("startTime")) + .build(); + if (StringUtils.isNotEmpty(containers.getString("type"))) { + containerCreateParams.setType(Type.valueOf(containers.getString("type"))); + } + if (StringUtils.isNotEmpty(containers.getString("command"))) { + containerCreateParams.setCommand(Splitter.on(" ").splitToList(containers.getString("command"))); + } + return containerCreateParams; + } + + /** + * 容器类型 + */ + public enum Type { + /** + * 主类别 + */ + MAIN("main"), + /** + * 子类别 + */ + SUB("sub"); + + private String value; + + Type(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/ContainerMountParams.java b/common/src/main/java/com/imitate/common/k8s/bean/ContainerMountParams.java new file mode 100644 index 0000000..f70560b --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/ContainerMountParams.java @@ -0,0 +1,28 @@ +package com.imitate.common.k8s.bean; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +/** + * 容器挂载参数 + * + * @author 威少 + */ +@Data +@ToString +@Builder +public class ContainerMountParams { + /** + * 数据卷名称 + */ + private String name; + /** + * 挂载目标位置 + */ + private String path; + /** + * 是否只读 + */ + private Boolean readonly; +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/CreatePodResult.java b/common/src/main/java/com/imitate/common/k8s/bean/CreatePodResult.java new file mode 100644 index 0000000..e56cb75 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/CreatePodResult.java @@ -0,0 +1,66 @@ +package com.imitate.common.k8s.bean; + +import io.fabric8.kubernetes.api.model.Pod; + +public class CreatePodResult { + + private Boolean success = Boolean.FALSE; + + private Integer status = CREATE_POD_STATUS_DEF; + + public static final int CREATE_POD_STATUS_DEF = 0; + public static final int CREATE_POD_STATUS_FAIL_PULL_IMAGE = 1; + public static final int CREATE_POD_STATUS_FAIL_DELETE_NOW = 2; + + private Pod pod; + + private String nodeName; + + /** + * 创建开始时间 + */ + private Long startTime; + + public CreatePodResult() { + } + + public CreatePodResult(Long startTime) { + this.startTime = startTime; + } + + public Pod getPod() { + return pod; + } + + public void setPod(Pod pod) { + this.pod = pod; + } + + public long getMillisecondCost() { + return System.currentTimeMillis() - startTime; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/DeploymentCreateParams.java b/common/src/main/java/com/imitate/common/k8s/bean/DeploymentCreateParams.java new file mode 100644 index 0000000..5ce5e5f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/DeploymentCreateParams.java @@ -0,0 +1,38 @@ +package com.imitate.common.k8s.bean; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +import java.util.Map; + +/** + * 创建Deployment的参数 + * + * @author 威少 + */ +@Data +@ToString +@Builder +public class DeploymentCreateParams { + /** + * Deployment的名字 + */ + private String name; + + /** + * 标签 + */ + private Map labels; + + /** + * pod副本数量 + */ + private Integer replicas; + + /** + * Pod Create Params + */ + private PodCreateParams podCreateParams; + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/ExecResultCase.java b/common/src/main/java/com/imitate/common/k8s/bean/ExecResultCase.java new file mode 100644 index 0000000..4464c5f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/ExecResultCase.java @@ -0,0 +1,122 @@ +package com.imitate.common.k8s.bean; + +/** + * Created by weishao on 2017/4/12. + */ +public class ExecResultCase { + private String caseId; + private String input; + private String output; + private String expectedOutput; + private String passed; + private String type = "0"; // 默认为文本类型 + + private Long testSetTime; + private Long testSetMem; + + public String getCaseId() { + return caseId; + } + + public String getInput() { + return input; + } + + public String getOutput() { + return output; + } + + public String getExpectedOutput() { + return expectedOutput; + } + + public String getPassed() { + return passed; + } + + public void setCaseId(String caseId) { + this.caseId = caseId; + } + + public void setInput(String input) { + this.input = input; + } + + public void setOutput(String output) { + this.output = output; + } + + public void setExpectedOutput(String expectedOutput) { + this.expectedOutput = expectedOutput; + } + + public void setPassed(String passed) { + this.passed = passed; + } + + public Long getTestSetTime() { + return testSetTime; + } + + public void setTestSetTime(Long testSetTime) { + this.testSetTime = testSetTime; + } + + public Long getTestSetMem() { + return testSetMem; + } + + public void setTestSetMem(Long testSetMem) { + this.testSetMem = testSetMem; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public ExecResultCase() { + } + + public ExecResultCase(String caseId, String output, String passed) { + this.caseId = caseId; + this.output = output; + this.passed = passed; + } + + public ExecResultCase(String caseId, String output, String passed, String type) { + this.caseId = caseId; + this.output = output; + this.passed = passed; + this.type = type; + } + + public enum Status { + /** + * 测试用例通过 + */ + PASS("1"), + /** + * 测试用例不通过 + */ + FAIL("0"); + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + Status(String value) { + this.value = value; + } + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/HPACreateParams.java b/common/src/main/java/com/imitate/common/k8s/bean/HPACreateParams.java new file mode 100644 index 0000000..5c197f6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/HPACreateParams.java @@ -0,0 +1,46 @@ +package com.imitate.common.k8s.bean; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +/** + * 创建HPA的参数 + * + * @author 威少 + */ +@Data +@ToString +@Builder +public class HPACreateParams { + /** + * hpa的名字 + */ + private String name; + + /** + * deployment的名字 + */ + private String deploymentName; + + /** + * 最小Pod数 + */ + private Integer minReplicas; + + /** + * 最大Pod数 + */ + private Integer maxReplicas; + + /** + * 期望的CPU利用率 + */ + private Integer desireCpuPercent; + + /** + * 期望的内存利用率 + */ + private Integer desireMemoryPercent; + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/NodeEvaErrorInfo.java b/common/src/main/java/com/imitate/common/k8s/bean/NodeEvaErrorInfo.java new file mode 100644 index 0000000..5bd5d22 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/NodeEvaErrorInfo.java @@ -0,0 +1,77 @@ +package com.imitate.common.k8s.bean; + +/** + * 节点评测错误信息 + */ +public class NodeEvaErrorInfo { + + private String nodeName; + + private Integer downloadErrorNum; + + private Integer createPodErrorNum; + + private Integer runErrorNum; + + private Integer evaCount; + + public boolean isErrorNode(Integer errorNum, Double errorRatio) { + return (createPodErrorNum > errorNum && ((double) createPodErrorNum / evaCount) > errorRatio) + || (runErrorNum > errorNum && ((double) runErrorNum / evaCount) > errorRatio); + } + + public boolean isRunErrorNode(Integer errorNum, Double errorRatio) { + return runErrorNum > errorNum && ((double) runErrorNum / evaCount) > errorRatio; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public Integer getDownloadErrorNum() { + return downloadErrorNum; + } + + public void setDownloadErrorNum(Integer downloadErrorNum) { + this.downloadErrorNum = downloadErrorNum; + } + + public Integer getCreatePodErrorNum() { + return createPodErrorNum; + } + + public void setCreatePodErrorNum(Integer createPodErrorNum) { + this.createPodErrorNum = createPodErrorNum; + } + + public Integer getRunErrorNum() { + return runErrorNum; + } + + public void setRunErrorNum(Integer runErrorNum) { + this.runErrorNum = runErrorNum; + } + + public Integer getEvaCount() { + return evaCount; + } + + public void setEvaCount(Integer evaCount) { + this.evaCount = evaCount; + } + + @Override + public String toString() { + return "NodeEvaErrorInfo{" + + "nodeName='" + nodeName + '\'' + + ", downloadErrorNum=" + downloadErrorNum + + ", createPodErrorNum=" + createPodErrorNum + + ", runErrorNum=" + runErrorNum + + ", evaCount=" + evaCount + + '}'; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/NodeQueryParam.java b/common/src/main/java/com/imitate/common/k8s/bean/NodeQueryParam.java new file mode 100644 index 0000000..eca0537 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/NodeQueryParam.java @@ -0,0 +1,35 @@ +package com.imitate.common.k8s.bean; + +import java.util.Map; + +public class NodeQueryParam { + + private String cluster; + private String name; + private Map labels; + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map getLabels() { + return labels; + } + + public void setLabels(Map labels) { + this.labels = labels; + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/NodeRes.java b/common/src/main/java/com/imitate/common/k8s/bean/NodeRes.java new file mode 100644 index 0000000..cdb9df3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/NodeRes.java @@ -0,0 +1,58 @@ +package com.imitate.common.k8s.bean; + +import com.google.common.util.concurrent.AtomicDouble; + +public class NodeRes { + + private String nodeName; + private Double requestCpu; + private Double allocatableCpu; + + private Double requestCpuRate; + + /** + * 调度时,使得pod尽量不调度到此节点的cpu量 + */ + private AtomicDouble unAffinityCpu = new AtomicDouble(0.0); + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public Double getRequestCpu() { + return requestCpu; + } + + public void setRequestCpu(Double requestCpu) { + this.requestCpu = requestCpu; + } + + public Double getAllocatableCpu() { + return allocatableCpu; + } + + public void setAllocatableCpu(Double allocatableCpu) { + this.allocatableCpu = allocatableCpu; + } + + public Double getRequestCpuRate() { + return requestCpuRate; + } + + public void setRequestCpuRate(Double requestCpuRate) { + this.requestCpuRate = requestCpuRate; + } + + public double getUnAffinityCpu() { + return unAffinityCpu.doubleValue(); + } + + public void addUnAffinityCpu(double unAffinityCpu) { + this.unAffinityCpu.addAndGet(unAffinityCpu); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/NodeResStat.java b/common/src/main/java/com/imitate/common/k8s/bean/NodeResStat.java new file mode 100644 index 0000000..f547ef0 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/NodeResStat.java @@ -0,0 +1,76 @@ +package com.imitate.common.k8s.bean; + +/** + * 节点资源统计,数据来源于 run_pod 表。 + * + * @author mumu + * + */ +public class NodeResStat { + + private String cluster; + private String nodeIp; + private Integer podNum; + + private Double cpuRequest; + private Double cpuLimit; + + private Double memoryRequest; + private Double momoryLimit; + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getNodeIp() { + return nodeIp; + } + + public void setNodeIp(String nodeIp) { + this.nodeIp = nodeIp; + } + + public Double getCpuRequest() { + return cpuRequest; + } + + public void setCpuRequest(Double cpuRequest) { + this.cpuRequest = cpuRequest; + } + + public Double getCpuLimit() { + return cpuLimit; + } + + public void setCpuLimit(Double cpuLimit) { + this.cpuLimit = cpuLimit; + } + + public Double getMemoryRequest() { + return memoryRequest; + } + + public void setMemoryRequest(Double memoryRequest) { + this.memoryRequest = memoryRequest; + } + + public Double getMomoryLimit() { + return momoryLimit; + } + + public void setMomoryLimit(Double momoryLimit) { + this.momoryLimit = momoryLimit; + } + + public Integer getPodNum() { + return podNum; + } + + public void setPodNum(Integer podNum) { + this.podNum = podNum; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/OjEvaResult.java b/common/src/main/java/com/imitate/common/k8s/bean/OjEvaResult.java new file mode 100644 index 0000000..6e58808 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/OjEvaResult.java @@ -0,0 +1,165 @@ +package com.imitate.common.k8s.bean; + +import java.util.List; + +/** + * oj评测结果 + */ +public class OjEvaResult { + + private String tpiID; + + private String status; + + private String execMode; + public final static String OJ_EVA_EXEC_MODE_DEBUG = "debug"; + public final static String OJ_EVA_EXEC_MODE_ALL = "submit"; + + /** + * 代码,返回上层,避免查数据库 + */ + private String codeFileContent; + private String executeMem; + private String executeTime; + private Integer failCaseNum; + private String outPut; + private ExecResultCase testCase; + private List execResultCases; + private TimeCost timeCost; + private String sec_key; + private String extras; + + // 新增编译和执行返回 0代表编译/运行失败 1 代表编译/运行成功 + private String compileSuccess; + private String executeSuccess; + + public OjEvaResult(String tpiID, String status, String outPut, ExecResultCase testCase, String sec_key, String extras) { + this.tpiID = tpiID; + this.status = status; + this.outPut = outPut; + this.testCase = testCase; + this.sec_key = sec_key; + this.extras = extras; + } + + public String getSec_key() { + return sec_key; + } + + public void setSec_key(String sec_key) { + this.sec_key = sec_key; + } + + public String getTpiID() { + return tpiID; + } + + public void setTpiID(String tpiID) { + this.tpiID = tpiID; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getOutPut() { + return outPut; + } + + public void setOutPut(String outPut) { + this.outPut = outPut; + } + + + public ExecResultCase getTestCase() { + return testCase; + } + + public void setTestCase(ExecResultCase testCase) { + this.testCase = testCase; + } + + public String getExecMode() { + return execMode; + } + + public void setExecMode(String execMode) { + this.execMode = execMode; + } + + public Integer getFailCaseNum() { + return failCaseNum; + } + + public void setFailCaseNum(Integer failCaseNum) { + this.failCaseNum = failCaseNum; + } + + public TimeCost getTimeCost() { + return timeCost; + } + + public void setTimeCost(TimeCost timeCost) { + this.timeCost = timeCost; + } + + public String getExecuteMem() { + return executeMem; + } + + public void setExecuteMem(String executeMem) { + this.executeMem = executeMem; + } + + public String getExecuteTime() { + return executeTime; + } + + public void setExecuteTime(String executeTime) { + this.executeTime = executeTime; + } + + public String getCodeFileContent() { + return codeFileContent; + } + + public void setCodeFileContent(String codeFileContent) { + this.codeFileContent = codeFileContent; + } + + public List getCases() { + return execResultCases; + } + + public void setCases(List execResultCases) { + this.execResultCases = execResultCases; + } + + public String getExtras() { + return extras; + } + + public void setExtras(String extras) { + this.extras = extras; + } + + public String getCompileSuccess() { + return compileSuccess; + } + + public void setCompileSuccess(String compileSuccess) { + this.compileSuccess = compileSuccess; + } + + public String getExecuteSuccess() { + return executeSuccess; + } + + public void setExecuteSuccess(String executeSuccess) { + this.executeSuccess = executeSuccess; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/PodCreateParams.java b/common/src/main/java/com/imitate/common/k8s/bean/PodCreateParams.java new file mode 100644 index 0000000..08805e6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/PodCreateParams.java @@ -0,0 +1,41 @@ +package com.imitate.common.k8s.bean; + +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.Volume; +import lombok.Data; +import lombok.ToString; + +import java.util.List; +import java.util.Map; + +/** + * 创建Pod的参数 + * + * @author 威少 + */ +@Data +@ToString +public class PodCreateParams { + /** + * 标签 + */ + private Map labels; + /** + * 名称 + */ + private String name; + /** + * 容器 + */ + private List containerList; + /** + * 节点选择器 + */ + private Map nodeSelector; + /** + * mount + */ + private Volume[] volumes; + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/PodCreateStrategy.java b/common/src/main/java/com/imitate/common/k8s/bean/PodCreateStrategy.java new file mode 100644 index 0000000..8c6cd8c --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/PodCreateStrategy.java @@ -0,0 +1,123 @@ +package com.imitate.common.k8s.bean; + + +import com.imitate.common.sys.constant.SysConfigCsts; + +import java.util.List; + +public class PodCreateStrategy { + + private Integer timeLimit = MAX_CREATE_POD_TIME_LIMIT; + + public static final int MAX_CREATE_POD_TIME_LIMIT = 15; + /** + * 普通pod能否分配到神龙节点 + */ + private Boolean normalPodCanShenlong; + + /** + * 普通pod能否分配到GPU节点 + */ + private Boolean normalPodCanGpu; + + /** + * 普通pod能否分配到oj节点 + */ + private Boolean normalPodCanOj; + + private List noSchedulableNodes; + + private List noSchedulableShenlongNodes; + + private List noSchedulableGpuNodes; + + /** + * pod不可调度的权重 + */ + private Integer podNoSchedulableWeight; + + private Integer autoscaleNodeWeight = SysConfigCsts.AUTOSCALE_NODE_WEIGHT_DEF; + + public Integer getTimeLimit() { + return timeLimit; + } + + public PodCreateStrategy setTimeLimit(Integer timeLimit) { + this.timeLimit = timeLimit; + return this; + } + + public Boolean getNormalPodCanShenlong() { + return normalPodCanShenlong; + } + + public PodCreateStrategy setNormalPodCanShenlong(Boolean normalPodCanShenlong) { + this.normalPodCanShenlong = normalPodCanShenlong; + return this; + } + + public Boolean getNormalPodCanGpu() { + return normalPodCanGpu; + } + + public PodCreateStrategy setNormalPodCanGpu(Boolean normalPodCanGpu) { + this.normalPodCanGpu = normalPodCanGpu; + return this; + } + + public Boolean getNormalPodCanOj() { + return normalPodCanOj; + } + + public PodCreateStrategy setNormalPodCanOj(Boolean normalPodCanOj) { + this.normalPodCanOj = normalPodCanOj; + return this; + } + + public List getNoSchedulableNodes() { + return noSchedulableNodes; + } + + public PodCreateStrategy setNoSchedulableNodes(List noSchedulableNodes) { + this.noSchedulableNodes = noSchedulableNodes; + return this; + } + + public List getNoSchedulableShenlongNodes() { + return noSchedulableShenlongNodes; + } + + public PodCreateStrategy setNoSchedulableShenlongNodes(List noSchedulableShenlongNodes) { + this.noSchedulableShenlongNodes = noSchedulableShenlongNodes; + return this; + } + + public List getNoSchedulableGpuNodes() { + return noSchedulableGpuNodes; + } + + public PodCreateStrategy setNoSchedulableGpuNodes(List noSchedulableGpuNodes) { + this.noSchedulableGpuNodes = noSchedulableGpuNodes; + return this; + } + + public Integer getPodNoSchedulableWeight() { + return podNoSchedulableWeight; + } + + public PodCreateStrategy setPodNoSchedulableWeight(Integer podNoSchedulableWeight) { + this.podNoSchedulableWeight = podNoSchedulableWeight; + return this; + } + + public Integer getAutoscaleNodeWeight() { + return autoscaleNodeWeight; + } + + public PodCreateStrategy setAutoscaleNodeWeight(Integer autoscaleNodeWeight) { + this.autoscaleNodeWeight = autoscaleNodeWeight; + return this; + } + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/PodQueryParam.java b/common/src/main/java/com/imitate/common/k8s/bean/PodQueryParam.java new file mode 100644 index 0000000..c643200 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/PodQueryParam.java @@ -0,0 +1,281 @@ +package com.imitate.common.k8s.bean; + + +import java.time.LocalDateTime; + +public class PodQueryParam { + private Long id; + private String cluster; + private String name; + private String tpiID; + + private String uid; + + private LocalDateTime requestTime; + private LocalDateTime minRequestTime; + private LocalDateTime maxRequestTime; + private String secKey; + + private String nodeName; + private String nodeIp; + + private String downloadStatus; + private String createPodStatus; + private String compileStatus; + private String runStatus; + private String status; + + private String sortField; + private String sortDirection; + + private Integer pageNum = 1; + private Integer pageSize = 10; + + private String statDate; + private Double pull; + private Double createPod; + + private String imageName; + + private Double cpuLimit; + + private Integer memoryLimit; + + private Double cpuRequest; + + private Integer memoryRequest; + + private Double errorRatio; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTpiID() { + return tpiID; + } + + public void setTpiID(String tpiID) { + this.tpiID = tpiID; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public LocalDateTime getRequestTime() { + return requestTime; + } + + public void setRequestTime(LocalDateTime requestTime) { + this.requestTime = requestTime; + } + + public LocalDateTime getMinRequestTime() { + return minRequestTime; + } + + public void setMinRequestTime(LocalDateTime minRequestTime) { + this.minRequestTime = minRequestTime; + } + + public LocalDateTime getMaxRequestTime() { + return maxRequestTime; + } + + public void setMaxRequestTime(LocalDateTime maxRequestTime) { + this.maxRequestTime = maxRequestTime; + } + + public String getSecKey() { + return secKey; + } + + public void setSecKey(String secKey) { + this.secKey = secKey; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getNodeIp() { + return nodeIp; + } + + public void setNodeIp(String nodeIp) { + this.nodeIp = nodeIp; + } + + public String getDownloadStatus() { + return downloadStatus; + } + + public void setDownloadStatus(String downloadStatus) { + this.downloadStatus = downloadStatus; + } + + public String getCreatePodStatus() { + return createPodStatus; + } + + public void setCreatePodStatus(String createPodStatus) { + this.createPodStatus = createPodStatus; + } + + public String getCompileStatus() { + return compileStatus; + } + + public void setCompileStatus(String compileStatus) { + this.compileStatus = compileStatus; + } + + public String getRunStatus() { + return runStatus; + } + + public void setRunStatus(String runStatus) { + this.runStatus = runStatus; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getSortField() { + return sortField; + } + + public void setSortField(String sortField) { + this.sortField = sortField; + } + + public String getSortDirection() { + return sortDirection; + } + + public void setSortDirection(String sortDirection) { + this.sortDirection = sortDirection; + } + + public Integer getPageNum() { + return pageNum; + } + + public void setPageNum(Integer pageNum) { + this.pageNum = pageNum; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public String getStatDate() { + return statDate; + } + + public void setStatDate(String statDate) { + this.statDate = statDate; + } + + public Double getPull() { + return pull; + } + + public void setPull(Double pull) { + this.pull = pull; + } + + public Double getCreatePod() { + return createPod; + } + + public void setCreatePod(Double createPod) { + this.createPod = createPod; + } + + public String getImageName() { + return imageName; + } + + public void setImageName(String imageName) { + this.imageName = imageName; + } + + public Double getCpuLimit() { + return cpuLimit; + } + + public void setCpuLimit(Double cpuLimit) { + this.cpuLimit = cpuLimit; + } + + public Integer getMemoryLimit() { + return memoryLimit; + } + + public void setMemoryLimit(Integer memoryLimit) { + this.memoryLimit = memoryLimit; + } + + public Double getCpuRequest() { + return cpuRequest; + } + + public void setCpuRequest(Double cpuRequest) { + this.cpuRequest = cpuRequest; + } + + public Integer getMemoryRequest() { + return memoryRequest; + } + + public void setMemoryRequest(Integer memoryRequest) { + this.memoryRequest = memoryRequest; + } + + public Double getErrorRatio() { + return errorRatio; + } + + public void setErrorRatio(Double errorRatio) { + this.errorRatio = errorRatio; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/RunPodQueryParam.java b/common/src/main/java/com/imitate/common/k8s/bean/RunPodQueryParam.java new file mode 100644 index 0000000..ae53bef --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/RunPodQueryParam.java @@ -0,0 +1,127 @@ +package com.imitate.common.k8s.bean; + +import java.time.LocalDateTime; +import java.util.List; + +public class RunPodQueryParam { + + private String cluster; + + private String name; + + private List names; + + private LocalDateTime expireTime; + + private Long priority; + + private LocalDateTime createTime; + + private String createBy; + + private String sshPort; + + private String svcPort; + + private Integer capacityThreshold; + + private String nodeName; + + private String nodeIp; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDateTime getExpireTime() { + return expireTime; + } + + public void setExpireTime(LocalDateTime expireTime) { + this.expireTime = expireTime; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public Long getPriority() { + return priority; + } + + public void setPriority(Long priority) { + this.priority = priority; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public Integer getCapacityThreshold() { + return capacityThreshold; + } + + public void setCapacityThreshold(Integer capacityThreshold) { + this.capacityThreshold = capacityThreshold; + } + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names = names; + } + + public String getSshPort() { + return sshPort; + } + + public void setSshPort(String sshPort) { + this.sshPort = sshPort; + } + + public String getSvcPort() { + return svcPort; + } + + public void setSvcPort(String svcPort) { + this.svcPort = svcPort; + } + + public String getNodeIp() { + return nodeIp; + } + + public void setNodeIp(String nodeIp) { + this.nodeIp = nodeIp; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/bean/ServiceCreateParams.java b/common/src/main/java/com/imitate/common/k8s/bean/ServiceCreateParams.java new file mode 100644 index 0000000..e8c5242 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/ServiceCreateParams.java @@ -0,0 +1,42 @@ +package com.imitate.common.k8s.bean; + +import lombok.Data; +import lombok.ToString; + +import java.util.Map; + +/** + * kubernetes service创建参数 + * + * @author 威少 + */ +@Data +@ToString +public class ServiceCreateParams { + /** + * 标签 + */ + private Map labels; + /** + * 名称 + */ + private String name; + /** + * 命名空间 + */ + private String namespace; + /** + * 选择器 + */ + private Map selector; + + /** + * service对外暴露端口 + */ + private Integer nodePort; + + /** + * 容器内部端口 + */ + private Integer targetPort; +} diff --git a/common/src/main/java/com/imitate/common/k8s/bean/TimeCost.java b/common/src/main/java/com/imitate/common/k8s/bean/TimeCost.java new file mode 100644 index 0000000..54289a3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/bean/TimeCost.java @@ -0,0 +1,81 @@ +package com.imitate.common.k8s.bean; + +import java.time.LocalDateTime; + +/** + * 评测各阶段时间记录 + */ +public class TimeCost { + + private LocalDateTime evaluateStartTime; + + private LocalDateTime evaluateEndTime; + + private String pull; + + private String createPod; + + private String execute; + + private String evaluateAllTime; + + public LocalDateTime getEvaluateStartTime() { + return evaluateStartTime; + } + + public void setEvaluateStartTime(LocalDateTime evaluateStartTime) { + this.evaluateStartTime = evaluateStartTime; + } + + public LocalDateTime getEvaluateEndTime() { + return evaluateEndTime; + } + + public void setEvaluateEndTime(LocalDateTime evaluateEndTime) { + this.evaluateEndTime = evaluateEndTime; + } + + public String getPull() { + return pull; + } + + public void setPull(String pull) { + this.pull = pull; + } + + public String getCreatePod() { + return createPod; + } + + public void setCreatePod(String createPod) { + this.createPod = createPod; + } + + public String getExecute() { + return execute; + } + + public void setExecute(String execute) { + this.execute = execute; + } + + public String getEvaluateAllTime() { + return evaluateAllTime; + } + + public void setEvaluateAllTime(String evaluateAllTime) { + this.evaluateAllTime = evaluateAllTime; + } + + @Override + public String toString() { + return "TimeCost{" + + "evaluateStartTime=" + evaluateStartTime + + ", evaluateEndTime=" + evaluateEndTime + + ", pull='" + pull + '\'' + + ", createPod='" + createPod + '\'' + + ", execute='" + execute + '\'' + + ", evaluateAllTime='" + evaluateAllTime + '\'' + + '}'; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/constant/BridgeNodeCsts.java b/common/src/main/java/com/imitate/common/k8s/constant/BridgeNodeCsts.java new file mode 100644 index 0000000..36c4b03 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/constant/BridgeNodeCsts.java @@ -0,0 +1,8 @@ +package com.imitate.common.k8s.constant; + +public interface BridgeNodeCsts { + + String NODE_LABEL_TYPE = "type"; + String NODE_LABEL_TYPE_OTHERS = "others"; + String NODE_LABEL_TYPE_UNDER_PRESSURE = "under-pressure"; +} diff --git a/common/src/main/java/com/imitate/common/k8s/constant/BridgePodCsts.java b/common/src/main/java/com/imitate/common/k8s/constant/BridgePodCsts.java new file mode 100644 index 0000000..36a006d --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/constant/BridgePodCsts.java @@ -0,0 +1,8 @@ +package com.imitate.common.k8s.constant; + +public interface BridgePodCsts { + + String STATUS_BEGIN = "0"; + String STATUS_END = "1"; + +} diff --git a/common/src/main/java/com/imitate/common/k8s/constant/PlatformConfigCsts.java b/common/src/main/java/com/imitate/common/k8s/constant/PlatformConfigCsts.java new file mode 100644 index 0000000..c4f674d --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/constant/PlatformConfigCsts.java @@ -0,0 +1,12 @@ +package com.imitate.common.k8s.constant; + +public interface PlatformConfigCsts { + + String PLATFORM_JAVA = "java"; + + String PLATFORM_C = "c"; + + String PLATFORM_CPP = "cpp"; + + String PLATFORM_PYTHON = "python"; +} diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/BridgePodMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/BridgePodMapper.java new file mode 100644 index 0000000..457316f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/BridgePodMapper.java @@ -0,0 +1,46 @@ +package com.imitate.common.k8s.mapper; + + + +import com.imitate.common.k8s.pojo.BridgePod; +import com.imitate.common.k8s.bean.NodeEvaErrorInfo; +import com.imitate.common.k8s.bean.PodQueryParam; +import com.imitate.common.k8s.pojo.EvaDayStat; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +@Mapper +public interface BridgePodMapper extends BaseMapper { + + @Override + int insert(BridgePod record); + + @Override + int insertSelective(BridgePod record); + + @Override + int updateByPrimaryKeySelective(BridgePod record); + + @Override + int updateByPrimaryKey(BridgePod record); + + List selectBridgePod(PodQueryParam param); + + void updateUpdateDeleteTime(BridgePod param); + + void deleteByRequestTime(LocalDateTime deleteTime); + + /** + * + * @param requestTime 整数,形式为yyyyMMdd + * @return + */ + EvaDayStat countEvaDayStat(Integer requestTime); + + List selectNodeEvaErrorInfos(LocalDateTime requestTime); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/ErrorPodInfoMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/ErrorPodInfoMapper.java new file mode 100644 index 0000000..008dc01 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/ErrorPodInfoMapper.java @@ -0,0 +1,14 @@ +package com.imitate.common.k8s.mapper; + +import com.imitate.common.k8s.pojo.ErrorPodInfo; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + + +@Repository +@Mapper +public interface ErrorPodInfoMapper extends BaseMapper { + @Override + int insert(ErrorPodInfo errorPodInfo); +} diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/EvaDayStatMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/EvaDayStatMapper.java new file mode 100644 index 0000000..48f6d62 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/EvaDayStatMapper.java @@ -0,0 +1,21 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.pojo.EvaDayStat; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +@Mapper +@Repository +public interface EvaDayStatMapper extends BaseMapper { + + + @Override + int insert(EvaDayStat record); + + @Override + int insertSelective(EvaDayStat record); + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/OjEvaDayStatMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/OjEvaDayStatMapper.java new file mode 100644 index 0000000..b136934 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/OjEvaDayStatMapper.java @@ -0,0 +1,16 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.pojo.OjEvaDayStat; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + + +@Repository +@Mapper +public interface OjEvaDayStatMapper extends BaseMapper { + + @Override + int insertSelective(OjEvaDayStat record); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/PlatformConfigMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/PlatformConfigMapper.java new file mode 100644 index 0000000..02326e8 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/PlatformConfigMapper.java @@ -0,0 +1,22 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.pojo.PlatformConfig; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@Mapper +public interface PlatformConfigMapper extends BaseMapper { + + @Override + int insertSelective(PlatformConfig record); + + @Override + int updateByPrimaryKeySelective(PlatformConfig record); + + List selectAllConfig(); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/RunPodMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/RunPodMapper.java new file mode 100644 index 0000000..adb4805 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/RunPodMapper.java @@ -0,0 +1,55 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.bean.NodeResStat; +import com.imitate.common.k8s.pojo.RunPod; +import com.imitate.common.k8s.bean.RunPodQueryParam; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +@Mapper +public interface RunPodMapper extends BaseMapper { + + int deleteByName(String name); + + int deleteByNameExpired(@Param("name") String name, @Param("expireTime") LocalDateTime expireTime); + + @Override + int insert(RunPod record); + + @Override + int insertSelective(RunPod record); + + + @Override + int updateByPrimaryKeySelective(RunPod record); + + void updateByNameSelective(RunPod runPod); + + int delayRunPodExpireTime(RunPod runPod); + + @Override + int updateByPrimaryKey(RunPod record); + + RunPod selectRunPodForUpdate(String podName); + + List selectRunPods(RunPodQueryParam param); + + int updatePortByName(RunPodQueryParam param); + + RunPod selectByName(String podName); + + RunPod selectSshRunPodByTpiID(String tpiID); + + List statNodeRes(RunPodQueryParam param); + + List podNumStatGroupByNodeIp(String cluster); + + void updateExpireTimeByNodeIp(RunPodQueryParam runPodQueryParam); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/SecurityContextConfigMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/SecurityContextConfigMapper.java new file mode 100644 index 0000000..13e7dac --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/SecurityContextConfigMapper.java @@ -0,0 +1,16 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.pojo.SecurityContextConfig; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@Mapper +public interface SecurityContextConfigMapper extends BaseMapper { + + List selectAll(); +} diff --git a/common/src/main/java/com/imitate/common/k8s/mapper/WindowsInfoMapper.java b/common/src/main/java/com/imitate/common/k8s/mapper/WindowsInfoMapper.java new file mode 100644 index 0000000..420357f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mapper/WindowsInfoMapper.java @@ -0,0 +1,31 @@ +package com.imitate.common.k8s.mapper; + + +import com.imitate.common.k8s.pojo.WindowsInfo; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@Mapper +public interface WindowsInfoMapper extends BaseMapper { + + int deleteByUniqId(String uniqId); + + @Override + int insertSelective(WindowsInfo record); + + WindowsInfo selectByUniqId(String uniqId); + + List selectByTpiId(String tpiId); + + List selectByUserId(String userID); + + int updateByUniqIdSelective(WindowsInfo windowsInfo); + + List selectUniqIdByAutoReleaseTime(WindowsInfo windowsInfo); + + List selectNotForwardEntryHost(); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/ClusterManager.java b/common/src/main/java/com/imitate/common/k8s/mgr/ClusterManager.java new file mode 100644 index 0000000..d2f2057 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/ClusterManager.java @@ -0,0 +1,173 @@ +package com.imitate.common.k8s.mgr; + + +import com.imitate.common.k8s.bean.BridgeContainer; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.NodeQueryParam; +import com.imitate.common.k8s.bean.NodeResStat; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.pojo.ClusterConfig; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import io.fabric8.kubernetes.api.model.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 集群管理 + */ +@Component +public class ClusterManager { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + // Map + private volatile Map clusterMap = new HashMap<>(); + + @Autowired + private K8sService k8sService; + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private RunPodService runPodService; + + @Autowired + private SysConfigService sysConfigService; + + // ClusterInfo 相关 begin ... + // 刷新集群节点 + public void refreshClusters() { + List ccList = clusterConfigService.getAvailableClusterConfigs(); + List nodeResStats = runPodService.statNodeRes(); + Map tmpMap = new HashMap<>(); + for (ClusterConfig cc : ccList) { + try { + ClusterInfo cInfo = refreshCluster(cc, nodeResStats); + tmpMap.put(cc.getName(), cInfo); + } catch (Exception e) { + logger.error("刷新单个集群节点信息失败, cluster: " + cc.getName(), e); + } + } + + clusterMap = tmpMap; + } + + private ClusterInfo refreshCluster(ClusterConfig cc, List nodeResStats) { + String cluster = cc.getName(); + NodeQueryParam param = new NodeQueryParam(); + param.setCluster(cluster); + List nodesTemp = k8sService.getNodes(param); + List nodes = new ArrayList<>(); + for (Node node : nodesTemp) { + if (K8sUtils.isReadyStatus(node)) { + nodes.add(node); + } + } + + ClusterInfo cInfo = new ClusterInfo(); + cInfo.setClusterConfig(cc); + cInfo.setWeight(cc.getWeight()); + cInfo.setAutoscale(cc.getAutoScale()); + cInfo.cal(cluster, nodes, nodeResStats); + + logger.debug("refreshCluster cInfo: {}", cInfo); + return cInfo; + } + + public Boolean canSupportedImageName(String cluster, String imageName) { + ClusterInfo cInfo = clusterMap.get(cluster); + if (cInfo != null) { + if (cInfo.getClusterConfig().supportImage(imageName) + && !cInfo.getClusterConfig().notSupportImage(imageName)) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + /** + * 获取支持镜像的集群 + * + * @param imageName + * @return + */ + public List getSupportedClusterInfo(String imageName) { + List list = new ArrayList<>(); + for (Map.Entry entry : clusterMap.entrySet()) { + ClusterInfo cInfo = entry.getValue(); + // 检查白名单 + if (!cInfo.getClusterConfig().supportImage(imageName)) { + continue; + } + // 过滤黑名单 + if (cInfo.getClusterConfig().notSupportImage(imageName)) { + continue; + } + list.add(cInfo); + } + + return list; + } + + public Map getClusterInfo() { + return clusterMap; + } + + public void clusterReserveRequestRes(String cluster, BridgeContainer bc) { + ClusterInfo clusterInfo = clusterMap.get(cluster); + clusterInfo.addReserveRequestCpu(bc.getCpuRequest()); + clusterInfo.addReserveRequestMemory(bc.getMemoryRequest()); + } + // ClusterInfo 相关 end ... + + // Node 相关 begin ... + public boolean isLocalNode(String cluster, String nodeName) { + Node node = getNode(cluster, nodeName); + + Map labelMap = node.getMetadata().getLabels(); + + return !labelMap.containsKey("remote"); + } + + public boolean isLocalCluster(String cluster) { + return clusterConfigService.getClusterConfig(cluster).getLocal(); + } + + public Node getNode(String cluster, String nodeName) { + ClusterInfo cInfo = clusterMap.get(cluster); + if (cInfo != null) { + Node node = cInfo.getNodeMap().get(nodeName); + if (node != null) { + return node; + } + } + + // 直接查询 + return k8sService.getNode(cluster, nodeName); + } + // Node 相关 end ... + + public int getScaleNodeWeight(String cluster) { + return sysConfigService.getScaleNodeWeight(); + } + + public Map getClusterNodeMap(String cluster) { + Map nodeMap = new HashMap<>(); + ClusterInfo clusterInfo = clusterMap.get(cluster); + if (clusterInfo != null) { + nodeMap = clusterInfo.getNodeMap(); + } + return nodeMap; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/NodeManager.java b/common/src/main/java/com/imitate/common/k8s/mgr/NodeManager.java new file mode 100644 index 0000000..5e8a7ca --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/NodeManager.java @@ -0,0 +1,146 @@ +package com.imitate.common.k8s.mgr; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.mgr.node.NodeMgrIf; +import com.imitate.common.k8s.mgr.node.OjNodeMgr; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.sys.service.SysConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * node管理 + */ +@Component +public class NodeManager { + + @Autowired + private SysConfigService sysConfigService; + + @Qualifier("normalNodeMgr") + @Autowired + private NodeMgrIf normalNodeMgr; + + @Qualifier("shenlongNodeMgr") + @Autowired + private NodeMgrIf shenlongNodeMgr; + + @Qualifier("gpuNodeMgr") + @Autowired + private NodeMgrIf gpuNodeMgr; + + @Qualifier("ojNodeMgr") + @Autowired + private NodeMgrIf ojNodeMgr; + + private AtomicBoolean checking = new AtomicBoolean(false); + + public void check() { + boolean r = checking.compareAndSet(false, true); + if (!r) {// 启动时初始化线程和定时任务,可能并发执行。只允许一个执行 + return; + } + try { + normalNodeMgr.check(); + shenlongNodeMgr.check(); + gpuNodeMgr.check(); + ojNodeMgr.check(); + } finally { + checking.set(false); + } + } + + /** + * 是否公共pod + * + * @param podName + * @param buildParams + * @return + */ + public boolean isCommonPod(String podName, JSONObject buildParams) { + String containers = buildParams.getString("containers"); + JSONArray jsonArray = JSONArray.parseArray(containers); + if (jsonArray.size() > 1) { + return false; + } + String imageName = jsonArray.getJSONObject(0).getString("image").split(":")[0]; + + List commonImageNames = sysConfigService.getCommonImageNames(); + return commonImageNames.contains(imageName) && podName.startsWith(TpCsts.TYPE_EVALUATE); + } + + // 神龙------------start------------- + public boolean canScheduleNormalPodToShenlong(String cluster, int timeLimit) { + return this.shenlongNodeMgr.canScheduleNormalPod(cluster, timeLimit); + } + + public void processShenlongPodStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + this.shenlongNodeMgr.processPodCreateStrategy(cluster, podCreateStrategy, containers); + } + + // 神龙------------end------------- + + + // GPU------------start------------- + public boolean canScheduleNormalPodToGpu(String cluster, int timeLimit) { + return this.gpuNodeMgr.canScheduleNormalPod(cluster, timeLimit); + } + + public void processGpuPodStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + this.gpuNodeMgr.processPodCreateStrategy(cluster, podCreateStrategy, containers); + } + // GPU------------end------------- + + // oj------------start------------- + public boolean canScheduleNormalPodToOj(String cluster, int timeLimit) { + return this.ojNodeMgr.canScheduleNormalPod(cluster, timeLimit); + } + + public void ojEvaStat(String cluster, String nodeName) { + ((OjNodeMgr) this.ojNodeMgr).ojEvaStat(cluster, nodeName); + } + + public void ojEvaTimeoutStat(String cluster, String nodeName) { + ((OjNodeMgr) this.ojNodeMgr).ojEvaTimeoutStat(cluster, nodeName); + } + + // oj------------end------------- + + public List getNoSchedulableNodes(String cluster) { + List list = new ArrayList<>(); + list.addAll(this.shenlongNodeMgr.getNoSchedulableNodes(cluster)); + list.addAll(this.gpuNodeMgr.getNoSchedulableNodes(cluster)); + list.addAll(this.ojNodeMgr.getNoSchedulableNodes(cluster)); + list.addAll(this.normalNodeMgr.getNoSchedulableNodes(cluster)); + return list; + } + + public void processCreatePodResult(String cluster, CreatePodResult result) { + boolean processed = this.shenlongNodeMgr.processCreatePodResult(cluster, result); + if (processed) { + return; + } + + processed = this.gpuNodeMgr.processCreatePodResult(cluster, result); + if (processed) { + return; + } + + processed = this.ojNodeMgr.processCreatePodResult(cluster, result); + if (processed) { + return; + } + + this.normalNodeMgr.processCreatePodResult(cluster, result); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/GpuNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/GpuNodeMgr.java new file mode 100644 index 0000000..23556d5 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/GpuNodeMgr.java @@ -0,0 +1,89 @@ +package com.imitate.common.k8s.mgr.node; + + +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.node.cluster.ClusterGpuNodeMgr; +import com.imitate.common.k8s.mgr.node.cluster.ClusterNodeMgrIf; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.bean.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * GPU节点管理 + */ +@Component +public class GpuNodeMgr implements NodeMgrIf { + + // Map + private final Map mgrMap = new HashMap<>(); + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private AppConfig appConfig; + + @Override + public void check() { + + // 没有Mgr的集群,初始化Mgr + Map clusterMap = clusterManager.getClusterInfo(); + for (ClusterInfo cInfo : clusterMap.values()) { + String cluster = cInfo.getClusterConfig().getName(); + if (mgrMap.get(cluster) == null) { + if (cInfo.getClusterConfig().notSupportImage(appConfig.getGpuImage())) { + continue; + } + + ClusterGpuNodeMgr mgr = BeanFactory.getObejct(ClusterGpuNodeMgr.class); + mgr.setCluster(cluster); + mgr.start(); + mgrMap.put(cluster, mgr); + } + } + + // 已经删除的集群,删除掉Mgr + for (String cluster : mgrMap.keySet()) { + if (!clusterMap.containsKey(cluster)) { + ClusterNodeMgrIf mgr = mgrMap.remove(cluster); + mgr.stop(); + } + } + } + + @Override + public boolean canScheduleNormalPod(String cluster, int timeLimit) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.canScheduleNormalPod(timeLimit); + } + + @Override + public List getNoSchedulableNodes(String cluster) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr == null ? Collections.emptyList() : mgr.getNoSchedulableNodes(); + } + + @Override + public void processPodCreateStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + mgr.processPodCreateStrategy(podCreateStrategy, containers); + } + } + + @Override + public boolean processCreatePodResult(String cluster, CreatePodResult result) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.processCreatePodResult(result); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/NodeMgrIf.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/NodeMgrIf.java new file mode 100644 index 0000000..39d5f41 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/NodeMgrIf.java @@ -0,0 +1,48 @@ +package com.imitate.common.k8s.mgr.node; + + +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; + +import java.util.List; + +/** + * node管理接口 + */ +public interface NodeMgrIf { + + /** + * 检测集群管理器等 + */ + void check(); + + /** + * 节点能否调度普通pod + * + * @return + */ + boolean canScheduleNormalPod(String cluster, int timeLimit); + + /** + * 获取不可调度的节点列表 + * + * @return + */ + List getNoSchedulableNodes(String cluster); + + /** + * 处理pod创建策略 + * + * @param podCreateStrategy + * @param containers + */ + void processPodCreateStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers); + + /** + * 处理创建pod结果 + * + * @param result + * @return 已经处理,返回true;不能处理,返回false。 + */ + boolean processCreatePodResult(String cluster, CreatePodResult result); +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/NormalNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/NormalNodeMgr.java new file mode 100644 index 0000000..1fda233 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/NormalNodeMgr.java @@ -0,0 +1,85 @@ +package com.imitate.common.k8s.mgr.node; + + +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.node.cluster.ClusterNodeMgrIf; +import com.imitate.common.k8s.mgr.node.cluster.ClusterNormalNodeMgr; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.bean.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 普通 node 管理 + */ +@Component +public class NormalNodeMgr implements NodeMgrIf { + + // Map + private final Map mgrMap = new HashMap<>(); + + @Autowired + private ClusterManager clusterManager; + + @Override + public void check() { + // 没有Mgr的集群,初始化Mgr + Map clusterMap = clusterManager.getClusterInfo(); + for (String cluster : clusterMap.keySet()) { + if (mgrMap.get(cluster) == null) { + ClusterNormalNodeMgr mgr = BeanFactory.getObejct(ClusterNormalNodeMgr.class); + mgr.setCluster(cluster); + mgr.start(); + mgrMap.put(cluster, mgr); + } + } + + // 已经删除的集群,删除掉Mgr + for (String cluster : mgrMap.keySet()) { + if (!clusterMap.containsKey(cluster)) { + ClusterNodeMgrIf mgr = mgrMap.remove(cluster); + mgr.stop(); + } + } + } + + @Override + public boolean canScheduleNormalPod(String cluster, int timeLimit) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.canScheduleNormalPod(timeLimit); + } + + /** + * 暂时只有超时的情况,不可调度 + * + * @return + */ + @Override + public List getNoSchedulableNodes(String cluster) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr == null ? Collections.emptyList() : mgr.getNoSchedulableNodes(); + } + + @Override + public void processPodCreateStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + mgr.processPodCreateStrategy(podCreateStrategy, containers); + } + } + + @Override + public boolean processCreatePodResult(String cluster, CreatePodResult result) { + + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.processCreatePodResult(result); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/OjNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/OjNodeMgr.java new file mode 100644 index 0000000..4acbf6f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/OjNodeMgr.java @@ -0,0 +1,107 @@ +package com.imitate.common.k8s.mgr.node; + + +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.node.cluster.ClusterNodeMgrIf; +import com.imitate.common.k8s.mgr.node.cluster.ClusterOjNodeMgr; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.bean.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * oj 节点管理 + */ +@Component +public class OjNodeMgr implements NodeMgrIf { + // Map + private final Map mgrMap = new HashMap<>(); + + @Autowired + private ClusterManager clusterManager; + + @Override + public void check() { + + // 没有Mgr的集群,初始化Mgr + Map clusterMap = clusterManager.getClusterInfo(); + for (ClusterInfo cInfo : clusterMap.values()) { + String cluster = cInfo.getClusterConfig().getName(); + if (mgrMap.get(cluster) == null) { + if (!cInfo.getClusterConfig().getLocal()) { + continue; + } + + ClusterOjNodeMgr mgr = BeanFactory.getObejct(ClusterOjNodeMgr.class); + mgr.setCluster(cluster); + mgr.start(); + mgrMap.put(cluster, mgr); + } + } + + // 已经删除的集群,删除掉Mgr + for (String cluster : mgrMap.keySet()) { + if (!clusterMap.containsKey(cluster)) { + ClusterNodeMgrIf mgr = mgrMap.remove(cluster); + mgr.stop(); + } + } + } + + public ClusterOjNodeMgr getClusterOjNodeMgr(String cluster) { + return (ClusterOjNodeMgr) mgrMap.get(cluster); + } + + public void clearOjEvaStat() { + for (ClusterNodeMgrIf mgr : mgrMap.values()) { + ((ClusterOjNodeMgr) mgr).clearOjEvaStat(); + } + } + + public void ojEvaStat(String cluster, String nodeName) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + ((ClusterOjNodeMgr) mgr).ojEvaStat(nodeName); + } + } + + public void ojEvaTimeoutStat(String cluster, String nodeName) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + ((ClusterOjNodeMgr) mgr).ojEvaTimeoutStat(nodeName); + } + } + + @Override + public boolean canScheduleNormalPod(String cluster, int timeLimit) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.canScheduleNormalPod(timeLimit); + } + + @Override + public List getNoSchedulableNodes(String cluster) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr == null ? Collections.emptyList() : mgr.getNoSchedulableNodes(); + } + + @Override + public void processPodCreateStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + mgr.processPodCreateStrategy(podCreateStrategy, containers); + } + } + + @Override + public boolean processCreatePodResult(String cluster, CreatePodResult result) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.processCreatePodResult(result); + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/ShenlongNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/ShenlongNodeMgr.java new file mode 100644 index 0000000..bf6cca9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/ShenlongNodeMgr.java @@ -0,0 +1,89 @@ +package com.imitate.common.k8s.mgr.node; + + +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.node.cluster.ClusterNodeMgrIf; +import com.imitate.common.k8s.mgr.node.cluster.ClusterShenlongNodeMgr; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.bean.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 神龙节点管理 + */ +@Component +public class ShenlongNodeMgr implements NodeMgrIf { + + // Map + private final Map mgrMap = new HashMap<>(); + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private AppConfig appConfig; + + @Override + public void check() { + + // 没有Mgr的集群,初始化Mgr + Map clusterMap = clusterManager.getClusterInfo(); + for (ClusterInfo cInfo : clusterMap.values()) { + String cluster = cInfo.getClusterConfig().getName(); + if (mgrMap.get(cluster) == null) { + if (cInfo.getClusterConfig().notSupportImage(appConfig.getShenlongImageCharacter())) { + continue; + } + + ClusterShenlongNodeMgr mgr = BeanFactory.getObejct(ClusterShenlongNodeMgr.class); + mgr.setCluster(cluster); + mgr.start(); + mgrMap.put(cluster, mgr); + } + } + + // 已经删除的集群,删除掉Mgr + for (String cluster : mgrMap.keySet()) { + if (!clusterMap.containsKey(cluster)) { + ClusterNodeMgrIf mgr = mgrMap.remove(cluster); + mgr.stop(); + } + } + } + + @Override + public boolean canScheduleNormalPod(String cluster, int timeLimit) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.canScheduleNormalPod(timeLimit); + } + + @Override + public List getNoSchedulableNodes(String cluster) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr == null ? Collections.emptyList() : mgr.getNoSchedulableNodes(); + } + + @Override + public void processPodCreateStrategy(String cluster, PodCreateStrategy podCreateStrategy, String containers) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + if (mgr != null) { + mgr.processPodCreateStrategy(podCreateStrategy, containers); + } + } + + @Override + public boolean processCreatePodResult(String cluster, CreatePodResult result) { + ClusterNodeMgrIf mgr = mgrMap.get(cluster); + return mgr != null && mgr.processCreatePodResult(result); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterGpuNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterGpuNodeMgr.java new file mode 100644 index 0000000..21aea49 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterGpuNodeMgr.java @@ -0,0 +1,311 @@ +package com.imitate.common.k8s.mgr.node.cluster; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.constant.BridgeNodeCsts; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.NodeRes; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.competitor.LockCompetitor; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.ThreadUtils; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 集群GPU节点管理 + */ +@Scope("prototype") // 每个集群一个实例 +@Component +public class ClusterGpuNodeMgr implements ClusterNodeMgrIf { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Map, 过载节点记录在此,到了恢复时间,即可重新分配pod + private final Map overloadMap = new ConcurrentHashMap<>(); + + // 所有 pod request cpu之和阈值,达到阈值,不再分配普通pod到gpu节点 + private volatile double requestCpuThreshold = 0; + // pod request cpu总量 + private volatile double requestCpuTotal = 0; + + // 节点资源,Map + private volatile Map nodeResMap = new HashMap<>(); + + private String cluster; + private volatile boolean terminated = false; + + @Qualifier("podScheduleLockCompetitor") + @Autowired + private LockCompetitor lockCompetitor; + + @Autowired + private K8sService k8sService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private AppConfig appConfig; + + @Override + public void start() { + new Thread(() -> { + this.checkNodePressure(); + }).start(); + + new Thread(() -> { + this.checkNodeRecovery(); + }).start(); + + } + + // 检测节点是否恢复 + private void checkNodeRecovery() { + while (!terminated) { + try { + if (lockCompetitor.winLock()) { + checkNodeRecoveryInner(); + } + } catch (Throwable t) { + logger.error("GPU节点超时到期后,尝试创建pod,以检测节点是否恢复正常出错, cluster:{}", cluster, t); + } finally { + ThreadUtils.sleep(60 * 1000); + } + } + } + + private void checkNodeRecoveryInner() { + long now = System.currentTimeMillis(); + Map tempMap = overloadMap; + Set keySet = tempMap.keySet(); + for (String nodeName : keySet) { + if (!nodeResMap.containsKey(nodeName)) { + continue; + } + if (tempMap.get(nodeName) > now) { + continue; + } + + int timeLimit = (int) sysConfigService.getCreatePodCostMaxTime() / 1000 / 2; + String podName = "checkpod-" + appConfig.getBridgeInstanceName(); + try { + Boolean success = k8sService.createCheckPod(cluster, podName, nodeName, timeLimit, + SysConfigCsts.CHECK_POD_CONTAINER_IMAGE); + if (success) { + k8sService.updateNodeLabel(cluster, nodeName, "type", "others"); + overloadMap.remove(nodeName); + logger.info("GPU节点创建检测pod成功,超时状态结束, 节点:{}, cluster:{}", nodeName, cluster); + } else { + overloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getGpuNodeOverloadExpireTime()); + logger.info("GPU节点创建检测pod失败,超时状态继续, 节点:{}, cluster:{}", nodeName, cluster); + } + } catch (Exception e) { + logger.error("检查GPU节点创建pod出错,image: {}, 节点:{}, cluster:{}", SysConfigCsts.CHECK_POD_CONTAINER_IMAGE, + nodeName, cluster, e); + } finally { + k8sService.deletePod(cluster, podName); + } + } + } + + private void checkNodePressure() { + while (!terminated) { + try { + double totalCpu = 0; // 所有节点cpu总量 + double requestCpuTotalTmp = 0; // 所有GPU pod request cpu总量 + Map nodeResMapTmp = new HashMap<>(); + + List nodes = k8sService.getNodeListWithLabel(cluster, TpCsts.GPU_NODE_LABEL_KEY, + TpCsts.GPU_NODE_LABEL_VALUE); + for (Node node : nodes) { + NodeRes nodeRes = new NodeRes(); + String nodeName = node.getMetadata().getName(); + nodeRes.setNodeName(nodeName); + + String val = K8sUtils.getLabel(node, "type"); + if (BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE.equals(val)) { + if (overloadMap.get(nodeName) == null) { + overloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getGpuNodeOverloadExpireTime()); + } + } else { + overloadMap.remove(nodeName); + } + + // 节点已分配 cpu + double nodeRequestCpu = 0; + List podList = k8sService.getPodListWithNodeName(cluster, nodeName); + for (Pod pod : podList) { + if (pod.getMetadata() != null && pod.getMetadata().getLabels() != null) { + String nodeRequire = pod.getMetadata().getLabels().get(TpCsts.NODE_REQUIRE); + if (appConfig.getGpuImage().equals(nodeRequire)) {// 神龙pod + double requestCpu = K8sUtils.getRequestCpu(pod); + nodeRequestCpu += requestCpu; + } + } + } + nodeRes.setRequestCpu(nodeRequestCpu); + + // 节点可分配cpu + double nodeCpu = K8sUtils.getNodeAllocatableCpu(node); + nodeRes.setAllocatableCpu(nodeCpu); + // 此节点GPU pod request cpu总量占可用cpu的比例 + double requestRate = nodeRequestCpu / nodeCpu; + nodeRes.setRequestCpuRate(requestRate); + nodeResMapTmp.put(nodeName, nodeRes); + + totalCpu += nodeCpu; // 增加cpu总量 + requestCpuTotalTmp += nodeRequestCpu; // 增加GPU pod request cpu总量 + } + + nodeResMap = nodeResMapTmp; + requestCpuTotal = requestCpuTotalTmp; + requestCpuThreshold = totalCpu * sysConfigService.getGpuOverloadPercent() / 100; + + removeNoLongerExistNode(); // 删除不再存在的节点 + } catch (Throwable t) { + logger.error("检查GPU节点压力出错, cluster:{}", cluster, t); + } finally { + ThreadUtils.sleep(5000); + } + } + + } + + /** + * 一些节点被回收,不再存在 + */ + private void removeNoLongerExistNode() { + Map tempMap = overloadMap; + Set keySet = tempMap.keySet(); + for (String key : keySet) { + if (!nodeResMap.containsKey(key)) { + overloadMap.remove(key); + } + } + } + + @Override + public void setCluster(String cluster) { + this.cluster = cluster; + } + + @Override + public boolean canScheduleNormalPod(int timeLimit) { + boolean openReuse = sysConfigService.getGpuNodeOpenReuse(); + if (!openReuse) { + return false; + } + logger.debug("canScheduleNormalPod requestCpuTotal: {}, requestCpuThreshold: {}", requestCpuTotal, requestCpuThreshold); + return requestCpuTotal < requestCpuThreshold; + } + + /** + * 暂时只有超时的情况,不可调度。 此处针对把普通pod调度到神龙节点。 + * + * @return + */ + @Override + public List getNoSchedulableNodes() { + return new ArrayList<>(overloadMap.keySet()); + } + + /** + * 处理pod调度策略 + * + * @param podCreateStrategy + */ + @Override + public void processPodCreateStrategy(PodCreateStrategy podCreateStrategy, String containers) { + boolean gpu = containers.contains(appConfig.getGpuImage());// 可在上一层处理 + if (!gpu) { + return; + } + + List list = new ArrayList<>(nodeResMap.values()); + if (list.size() <= 1) { + return; + } + Collections.sort(list, new Comparator() { + @Override + public int compare(NodeRes o1, NodeRes o2) { + return o1.getRequestCpuRate().compareTo(o2.getRequestCpuRate()); + } + }); + + // bridge规模扩大时,必要改为集中分配 + double requestCpu = ContainerUtil.getRequestCpu(containers); + boolean existNoSchedulableNode = true; // 存在不可调度的节点 + List noList = new ArrayList<>(); + for (int i = list.size() - 1; i > 0; i--) { + NodeRes higher = list.get(i); + NodeRes least = list.get(0); + + double unAffinityCpu = requestCpu + higher.getUnAffinityCpu(); + // k8s是整体调度的,可能把很多GPU pod调度到资源较小的节点,普通pod调度到资源较大的节点。这时候更加需要把GPU pod往资源较大节点分配。 + // 例如把3个GPU pod分配到8c节点,32c节点一个GPU pod都没有。 + double allocatableCpu = least.getAllocatableCpu(); + // 当前是2台bridge节点,每台可以调节50%的量, 再乘0.8是因为pod本来倾向于分配到较小节点 + if (allocatableCpu * (higher.getRequestCpuRate() - least.getRequestCpuRate()) * 0.5 * 0.8 > unAffinityCpu) { + noList.add(higher.getNodeName()); + higher.addUnAffinityCpu(requestCpu); + } else { + existNoSchedulableNode = false; + } + + // 3种情况检测结束:(1)没有更多不可调度的节点。(2)不可调度的节点数量超过9。(3)已经有超过一半的节点不可调度。 + if (!existNoSchedulableNode || noList.size() > 9 || noList.size() >= list.size() / 2) { + break; + } + } + + // 此处是针对GPU pod + podCreateStrategy.setNoSchedulableGpuNodes(noList); + + } + + @Override + public boolean processCreatePodResult(CreatePodResult result) { + String nodeName = K8sUtils.getNodeName(result.getPod()); + if (StringUtils.isBlank(nodeName)) { + return false;// 调度失败,连节点都没有 + } + + if (!nodeResMap.containsKey(nodeName)) { // 不是GPU节点 + return false; + } + + if (Boolean.FALSE.equals(result.getSuccess()) + && result.getMillisecondCost() >= sysConfigService.getCreatePodCostMaxTime()) { + overloadMap.put(nodeName, System.currentTimeMillis() + sysConfigService.getGpuNodeOverloadExpireTime()); + k8sService.updateNodeLabel(cluster, nodeName, BridgeNodeCsts.NODE_LABEL_TYPE, + BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE); + logger.info("创建pod 超时 {}s,GPU节点超时状态开始,更改type label,pod:{}, node:{}, cluster:{}", + result.getMillisecondCost() / 1000, K8sUtils.getPodName(result.getPod()), nodeName, cluster); + } + + return true; + + } + + @Override + public void stop() { + terminated = true; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNodeMgrIf.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNodeMgrIf.java new file mode 100644 index 0000000..66de8b3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNodeMgrIf.java @@ -0,0 +1,61 @@ +package com.imitate.common.k8s.mgr.node.cluster; + + +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; + +import java.util.List; + +/** + * 集群节点管理接口。 + */ +public interface ClusterNodeMgrIf { + /** + * 启动管理器 + */ + void start(); + + /** + * 集群 + * + * @param cluster + * @return + */ + void setCluster(String cluster); + + /** + * 节点能否调度普通pod + * + * @return + */ + boolean canScheduleNormalPod(int timeLimit); + + /** + * 获取不可调度的节点列表 + * + * @return + */ + List getNoSchedulableNodes(); + + /** + * 处理pod创建策略 + * + * @param podCreateStrategy + * @param containers + */ + void processPodCreateStrategy(PodCreateStrategy podCreateStrategy, String containers); + + /** + * 处理创建pod结果 + * + * @param result + * @return 已经处理,返回true;不能处理,返回false。 + */ + boolean processCreatePodResult(CreatePodResult result); + + /** + * 停止管理器 + */ + void stop(); +} + diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNormalNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNormalNodeMgr.java new file mode 100644 index 0000000..636dd29 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterNormalNodeMgr.java @@ -0,0 +1,114 @@ +package com.imitate.common.k8s.mgr.node.cluster; + + +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.ThreadUtils; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 集群普通节点管理。 + */ +@Scope("prototype") // 每个集群一个实例 +@Component +public class ClusterNormalNodeMgr implements ClusterNodeMgrIf { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Map, 过载节点记录在此,到了恢复时间,即可重新分配pod + private final Map normalNodeOverloadMap = new ConcurrentHashMap<>(); + // 集群 + private String cluster; + private volatile boolean terminated = false; + + @Autowired + private SysConfigService sysConfigService; + + @Override + public void start() { + new Thread(() -> { + this.doWork(); + }).start(); + } + + private void doWork() { + while (!terminated) { + try { + checkoutNormalRecovery(); + } catch (Throwable e) { + logger.error("检查普通节点恢复正常出错, cluster:{}", cluster, e); + } finally { + ThreadUtils.sleep(5000); + } + } + } + + private void checkoutNormalRecovery() { + long now = System.currentTimeMillis(); + Map tempMap = normalNodeOverloadMap; + Set keySet = tempMap.keySet(); + for (String key : keySet) { + if (tempMap.get(key) <= now) { + normalNodeOverloadMap.remove(key); + } + } + } + + @Override + public void setCluster(String cluster) { + this.cluster = cluster; + } + + @Override + public boolean canScheduleNormalPod(int timeLimit) { + return true; + } + + @Override + public List getNoSchedulableNodes() { + return new ArrayList<>(normalNodeOverloadMap.keySet()); + } + + @Override + public void processPodCreateStrategy(PodCreateStrategy podCreateStrategy, String containers) { + throw new UnsupportedOperationException("ClusterNormalNodeMgr 不支持的操作"); + } + + @Override + public boolean processCreatePodResult(CreatePodResult result) { + Pod pod = result.getPod(); + String nodeName = K8sUtils.getNodeName(pod); + if (StringUtils.isBlank(nodeName)) { + return false; // 调度失败,连节点都没有 + } + + if (Boolean.FALSE.equals(result.getSuccess()) + && result.getMillisecondCost() >= sysConfigService.getCreatePodCostMaxTime()) { + normalNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getNodeOverloadExpireTime()); + logger.info("创建 pod 超时, cluster:{}, pod:{}, 耗时:{}s,节点{}超时状态开始", cluster, K8sUtils.getPodName(pod), + result.getMillisecondCost() / 1000, nodeName); + } + + return true; + } + + @Override + public void stop() { + terminated = true; + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterOjNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterOjNodeMgr.java new file mode 100644 index 0000000..0faaa17 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterOjNodeMgr.java @@ -0,0 +1,348 @@ +package com.imitate.common.k8s.mgr.node.cluster; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.constant.BridgeNodeCsts; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.competitor.LockCompetitor; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.ThreadUtils; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 集群 oj 节点管理 + */ +@Scope("prototype") // 每个集群一个实例 +@Component +public class ClusterOjNodeMgr implements ClusterNodeMgrIf { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Map, 过载节点记录在此,到了恢复时间,即可重新分配pod + private final Map ojNodeOverloadMap = new ConcurrentHashMap<>(); + + private final int POD_CAN_TO_OJ_MAX_TIME_LIMIT = 10; + + private final String OJ_EVA_STAT_REDIS_CACHE_KEY = "ojEvaStat_"; + + private final String OJ_EVA_TIMEOUT_STAT_REDIS_CACHE_KEY = "ojEvaTimeoutStat_"; + + // HashMap即可,当前只要检测单线程访问 + private final Map ojNodeEvaCountMap = new ConcurrentHashMap<>(); + + private final Map ojNodeEvaTimeoutCountMap = new ConcurrentHashMap<>(); + + private volatile long updateEvaTimeoutCountTimestamp = System.currentTimeMillis(); + + private volatile Map ojNodeMap = new ConcurrentHashMap<>(); + + // 集群 + private String cluster; + private volatile boolean terminated = false; + + @Qualifier("podScheduleLockCompetitor") + @Autowired + private LockCompetitor lockCompetitor; + + @Autowired + private K8sService k8sService; + + @Autowired + private SysConfigService sysConfigService; + + @Override + public void start() { +// new Thread(() -> { +// this.checkOjNodePressure(); +// }).start(); + } + + private void checkOjNodePressure() { + while (!terminated) { + try { + long nowTime = System.currentTimeMillis(); + boolean cycleOver = nowTime - updateEvaTimeoutCountTimestamp > 10 * 1000;// 周期结束 + if (cycleOver) { + updateEvaTimeoutCountTimestamp = nowTime; + } + Map ojNodeMapTmp = new HashMap<>(); + + // 获取oj节点 + List nodes = k8sService.getNodeListWithLabel(cluster, TpCsts.OJ_LABEL_KEY, TpCsts.OJ_LABEL_VALUE); + // 更新节点压力情况 + for (Node node : nodes) { + String nodeName = node.getMetadata().getName(); + ojNodeMapTmp.put(nodeName, node); + if (lockCompetitor.winLock()) {// bridge调度中心节点 + if (ojNodeOverloadMap.get(nodeName) == null) { + checkOjNodePressure(node); + } else if (ojNodeOverloadMap.get(nodeName) < nowTime) {// 压力时间已过 + checkOjNodePressure(node); + } + } else {// bridge普通节点 + String val = K8sUtils.getLabel(node, "type"); + if (BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE.equals(val)) { + if (ojNodeOverloadMap.get(nodeName) == null) { + ojNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getOjNodeOverloadExpireTime()); + } + } else { + ojNodeOverloadMap.remove(nodeName); + } + } + + changeCount(nodeName, cycleOver); + } + ojNodeMap = ojNodeMapTmp; + + // 检查压力node是否仍然存在 + for (String nodeName : ojNodeOverloadMap.keySet()) { + boolean found = false; + for (Node node : nodes) { + String name = node.getMetadata().getName(); + if (nodeName.equals(name)) { + found = true; + break; + } + } + if (!found) {// 如果压力节点已经从k8s集群删除,则删除掉 + ojNodeOverloadMap.remove(nodeName); + } + } + + } catch (Throwable t) { + logger.error("检查oj节点压力出错, cluster:{}", cluster, t); + } finally { + ThreadUtils.sleep(5000); + } + } + } + + private void checkOjNodePressure(Node node) { + boolean isOverload = isOjNodeOverload(cluster, node); + String nodeName = node.getMetadata().getName(); + boolean isEvaTimeout = isOjNodeEvaTimeout(nodeName); + + if (isOverload || isEvaTimeout) { + if (ojNodeOverloadMap.get(nodeName) == null) { +// k8sService.updateNodeLabel(cluster, nodeName, "type", BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE); + logger.info("oj节点压力过大, cluster:{}, nodeName:{}, isOverload: {}, isEvaTimeout:{}", cluster, + nodeName, isOverload, isEvaTimeout); + } else { + logger.info("oj节点有压力,超时状态继续, cluster:{}, nodeName:{}, isOverload: {}, isEvaTimeout:{}", cluster, + nodeName, isOverload, isEvaTimeout); + } + ojNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getOjNodeOverloadExpireTime()); + } else { + if (ojNodeOverloadMap.get(nodeName) != null) { +// k8sService.updateNodeLabel(cluster, nodeName, "type", "others"); + ojNodeOverloadMap.remove(nodeName); + logger.info("oj节点压力恢复正常, cluster:{}, nodeName:{}", cluster, nodeName); + } + } + + } + + /** + * 更改计数 + * + * @param nodeName + * @param cycleOver + */ + private void changeCount(String nodeName, boolean cycleOver) { + // 超时计数 + if (cycleOver) { + String newOjEvaTimeoutCountStr = JedisUtil.get(OJ_EVA_TIMEOUT_STAT_REDIS_CACHE_KEY + nodeName); + int newOjEvaTimeoutCount = StringUtils.isNotEmpty(newOjEvaTimeoutCountStr) + ? Integer.parseInt(newOjEvaTimeoutCountStr) + : 0; + ojNodeEvaTimeoutCountMap.put(nodeName, newOjEvaTimeoutCount); + } + + // 评测计数 + String newOjEvaCountStr = JedisUtil.get(OJ_EVA_STAT_REDIS_CACHE_KEY + nodeName); + int newOjEvaCount = StringUtils.isNotEmpty(newOjEvaCountStr) ? Integer.parseInt(newOjEvaCountStr) : 0; + ojNodeEvaCountMap.put(nodeName, newOjEvaCount); + + } + + private boolean isOjNodeEvaTimeout(String nodeName) { + String newOjEvaTimeoutCountStr = JedisUtil.get(OJ_EVA_TIMEOUT_STAT_REDIS_CACHE_KEY + nodeName); + int newOjEvaTimeoutCount = StringUtils.isNotEmpty(newOjEvaTimeoutCountStr) + ? Integer.parseInt(newOjEvaTimeoutCountStr) + : 0; + + Integer ojEvaTimeoutCount = ojNodeEvaTimeoutCountMap.get(nodeName) != null + ? ojNodeEvaTimeoutCountMap.get(nodeName) + : 0; + + int maxEvaTimeoutNum = sysConfigService.getOjNodeMaxEvaTimeoutNum(); + logger.debug( + "oj超时检测详情, cluster:{}, nodeName:{}, newOjEvaTimeoutCount: {}, ojEvaTimeoutCount:{}, maxEvaTimeoutNum:{}", + cluster, nodeName, newOjEvaTimeoutCount, ojEvaTimeoutCount, maxEvaTimeoutNum); + + int timeoutNum = newOjEvaTimeoutCount - ojEvaTimeoutCount; + return timeoutNum > maxEvaTimeoutNum; + } + + private boolean isOjNodeOverload(final String cluster, Node node) { + String nodeName = node.getMetadata().getName(); + List podList = k8sService.getPodListReversely(cluster, nodeName, TpCsts.OJ_LABEL_KEY, TpCsts.OJ_LABEL_VALUE); + + double nodeCPU = K8sUtils.getNodeCapacityCpu(node); + // 根据节点cpu与最大可使用算力百分比计算最大可使用算力(8核节点算力为100) + double ojNodePowerThreshold = nodeCPU / 8 * sysConfigService.getOjNodeMaxPowPercent(); + + // 计算普通pod使用算力(普通pod评测使用算力为3) + int evaluatePodUsedPower = podList.size() * 8; + + // 计算oj pod使用算力 + int ojPodUsedPower = getOjPodUsedPower(nodeName); + + // 当前node实际使用算力 + int ojNodeUsedPower = evaluatePodUsedPower + ojPodUsedPower; + logger.debug( + "oj压力检测详情, cluster:{}, nodeName:{}, ojNodePowerThreshold: {}, evaluatePodUsedPower:{}, ojPodUsedPower:{}", + cluster, nodeName, ojNodePowerThreshold, evaluatePodUsedPower, ojPodUsedPower); + + return ojNodeUsedPower > ojNodePowerThreshold; + } + + private int getOjPodUsedPower(String nodeName) { + String newOjEvaCountStr = JedisUtil.get(OJ_EVA_STAT_REDIS_CACHE_KEY + nodeName); + int newOjEvaCount = StringUtils.isNotEmpty(newOjEvaCountStr) ? Integer.parseInt(newOjEvaCountStr) : 0; + Integer ojEvaCount = ojNodeEvaCountMap.get(nodeName) != null ? ojNodeEvaCountMap.get(nodeName) : 0; + + return newOjEvaCount - ojEvaCount; + } + + /** + * 清理评测统计 + */ + public void clearOjEvaStat() { + // 获取oj节点 + List nodes = k8sService.getNodeListWithLabel(cluster, TpCsts.OJ_LABEL_KEY, TpCsts.OJ_LABEL_VALUE); + + for (Node node : nodes) { + String nodeName = node.getMetadata().getName(); + JedisUtil.del(OJ_EVA_STAT_REDIS_CACHE_KEY + nodeName); + ojNodeEvaCountMap.put(nodeName, 0); + + JedisUtil.del(OJ_EVA_TIMEOUT_STAT_REDIS_CACHE_KEY + nodeName); + ojNodeEvaTimeoutCountMap.put(nodeName, 0); + } + } + + public void ojEvaStat(String nodeName) { + JedisUtil.incrBy(OJ_EVA_STAT_REDIS_CACHE_KEY + nodeName, 1); + } + + public void ojEvaTimeoutStat(String nodeName) { + JedisUtil.incrBy(OJ_EVA_TIMEOUT_STAT_REDIS_CACHE_KEY + nodeName, 1); + } + + @Override + public void setCluster(String cluster) { + this.cluster = cluster; + } + + @Override + public boolean canScheduleNormalPod(int timeLimit) { + return (timeLimit <= POD_CAN_TO_OJ_MAX_TIME_LIMIT) && sysConfigService.getOjNodeOpenReuse() + && ojNodeMap.size() > ojNodeOverloadMap.size(); + } + + /** + * 暂时只有超时的情况,不可调度 + * + * @return + */ + @Override + public List getNoSchedulableNodes() { + return new ArrayList<>(ojNodeOverloadMap.keySet()); + } + + /** + * 处理pod调度策略 + * + * @param podCreateStrategy + */ + @Override + public void processPodCreateStrategy(PodCreateStrategy podCreateStrategy, String containers) { + throw new UnsupportedOperationException("oj 共享pod, 不支持processPodCreateStrategy"); + } + + @Override + public boolean processCreatePodResult(CreatePodResult result) { + String nodeName = K8sUtils.getNodeName(result.getPod()); + if (StringUtils.isBlank(nodeName)) { + return false;// 调度失败,连节点都没有 + } + + if (!ojNodeMap.containsKey(nodeName)) { // 不是oj节点 + return false; + } + + if (Boolean.FALSE.equals(result.getSuccess()) + && result.getMillisecondCost() >= sysConfigService.getCreatePodCostMaxTime()) { + + ojNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getOjNodeOverloadExpireTime()); +// k8sService.updateNodeLabel(cluster, nodeName, "type", BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE); + logger.info("创建pod {} 超时 {}s,oj节点{}超时状态开始, cluster:{}", K8sUtils.getPodName(result.getPod()), + result.getMillisecondCost() / 1000, nodeName, cluster); + } + + return true; + + } + + @Override + public void stop() { + terminated = true; + } + + public Double ojNodeOverloadRatio() { + if (ojNodeMap.size() > 0) { + return ojNodeOverloadMap.size() / (double) ojNodeMap.size(); + } + return null; + } + + public String getNoOverloadOjNodeName() { + Map ojNodeMapTemp = ojNodeMap; + Set keySet = ojNodeMapTemp.keySet(); + for (String nodeName : keySet) { + if (!ojNodeOverloadMap.containsKey(nodeName)) { + Node node = ojNodeMapTemp.get(nodeName); + String capacityLabelValue = K8sUtils.getLabel(node, TpCsts.OJ_CAPACITY_NODE_LABEL_KEY); + if (StringUtils.isNotEmpty(capacityLabelValue) + && LocalDateTime.now().isAfter(LocalDateTime.parse(capacityLabelValue, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))) { + return nodeName; + } + } + } + return null; + } + + public int getOjNodeSize() { + return ojNodeMap.size(); + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterShenlongNodeMgr.java b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterShenlongNodeMgr.java new file mode 100644 index 0000000..feea767 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/mgr/node/cluster/ClusterShenlongNodeMgr.java @@ -0,0 +1,299 @@ +package com.imitate.common.k8s.mgr.node.cluster; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.k8s.constant.BridgeNodeCsts; +import com.imitate.common.k8s.bean.NodeRes; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.competitor.LockCompetitor; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.ThreadUtils; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 集群神龙节点管理 + */ +@Scope("prototype") // 每个集群一个实例 +@Component +public class ClusterShenlongNodeMgr implements ClusterNodeMgrIf { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Map, 过载节点记录在此,到了恢复时间,即可重新分配pod + private final Map shenlongNodeOverloadMap = new ConcurrentHashMap<>(); + + // 所有神龙节点request cpu之和阈值,达到阈值,不再分配普通pod到神龙节点 + private volatile double shenlongNodeRequestCpuThreshold = 0; + // 神龙pod request cpu总量 + private volatile double shenlongPodRequestCpuTotal = 0; + + // 神龙节点资源,Map + private volatile Map shenlongNodeResMap = new HashMap<>(); + + private final String CHECK_POD_CONTAINER_IMAGE = "gcc-ssh:v1.0"; + + private String cluster; + private volatile boolean terminated = false; + + @Qualifier("podScheduleLockCompetitor") + @Autowired + private LockCompetitor lockCompetitor; + + @Autowired + private K8sService k8sService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private AppConfig appConfig; + + @Override + public void start() { + new Thread(() -> { + this.checkShenlongNodePressure(); + }).start(); + + new Thread(() -> { + this.checkShenlongNodeRecovery(); + }).start(); + + } + + // 检测神龙节点是否恢复 + private void checkShenlongNodeRecovery() { + while (!terminated) { + try { + if (lockCompetitor.winLock()) { + checkShenlongNodeRecoveryInner(); + } + } catch (Throwable t) { + logger.error("神龙节点超时到期后,尝试创建pod,以检测节点是否恢复正常出错, cluster:{}", cluster, t); + } finally { + ThreadUtils.sleep(60 * 1000); + } + } + } + + private void checkShenlongNodeRecoveryInner() { + long now = System.currentTimeMillis(); + Map tempMap = shenlongNodeOverloadMap; + Set keySet = tempMap.keySet(); + for (String nodeName : keySet) { + if (!shenlongNodeResMap.containsKey(nodeName)) { + continue; + } + if (tempMap.get(nodeName) > now) { + continue; + } + + int timeLimit = (int) sysConfigService.getCreatePodCostMaxTime() / 1000 / 2; + String podName = "checkpod-" + appConfig.getBridgeInstanceName(); + try { + Boolean shenlongNodeCreatePodFlag = k8sService.createCheckPod(cluster, podName, nodeName, timeLimit, + CHECK_POD_CONTAINER_IMAGE); + if (shenlongNodeCreatePodFlag) { + k8sService.updateNodeLabel(cluster, nodeName, "type", "others"); + shenlongNodeOverloadMap.remove(nodeName); + logger.info("神龙节点{}创建检测pod成功,超时状态结束, cluster:{}", nodeName, cluster); + } else { + shenlongNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getShenlongNodeOverloadExpireTime()); + logger.info("神龙节点{}创建检测pod失败,超时状态继续, cluster:{}", nodeName, cluster); + } + } catch (Exception e) { + logger.error("检查神龙节点创建pod出错,image: {}, cluster:{}", CHECK_POD_CONTAINER_IMAGE, cluster, e); + } finally { + k8sService.deletePod(cluster, podName); + } + } + } + + private void checkShenlongNodePressure() { + while (!terminated) { + try { + double totalCpu = 0; // 所有神龙节点,cpu总量 + double shenlongPodRequestCpu = 0; // 所有神龙pod request cpu总量 + Map nodeResMapTmp = new HashMap<>(); + + List nodes = k8sService.getNodeListWithLabel(cluster, TpCsts.SHENLONG_NODE_LABEL_KEY, + TpCsts.SHENLONG_NODE_LABEL_VALUE); + for (Node node : nodes) { + NodeRes nodeRes = new NodeRes(); + String nodeName = node.getMetadata().getName(); + nodeRes.setNodeName(nodeName); + + String val = K8sUtils.getLabel(node, "type"); + if (BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE.equals(val)) { + if (shenlongNodeOverloadMap.get(nodeName) == null) { + shenlongNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getShenlongNodeOverloadExpireTime()); + } + } else { + shenlongNodeOverloadMap.remove(nodeName); + } + + // 节点已分配 cpu + double requestCpuTotal = 0; + List podList = k8sService.getPodListWithNodeName(cluster, nodeName); + for (Pod pod : podList) { + if (pod.getMetadata() != null && pod.getMetadata().getLabels() != null) { + String nodeRequire = pod.getMetadata().getLabels().get(TpCsts.NODE_REQUIRE); + if (appConfig.getShenlongImageCharacter().equals(nodeRequire)) {// 神龙pod + double requestCpu = K8sUtils.getRequestCpu(pod); + requestCpuTotal += requestCpu; + } + } + } + nodeRes.setRequestCpu(requestCpuTotal); + + // 节点可分配cpu + double nodeCpu = K8sUtils.getNodeAllocatableCpu(node); + nodeRes.setAllocatableCpu(nodeCpu); + // 此节点神龙pod request cpu总量占可用cpu的比例 + double requestRate = requestCpuTotal / nodeCpu; + nodeRes.setRequestCpuRate(requestRate); + nodeResMapTmp.put(nodeName, nodeRes); + + totalCpu += nodeCpu; // 增加cpu总量 + shenlongPodRequestCpu += requestCpuTotal; // 增加神龙pod request cpu总量 + } + + shenlongNodeResMap = nodeResMapTmp; + shenlongPodRequestCpuTotal = shenlongPodRequestCpu; + shenlongNodeRequestCpuThreshold = totalCpu * sysConfigService.getShenlongOverloadPercent() / 100; + + removeNoLongerExistNode(); // 删除不再存在的神龙节点 + } catch (Throwable t) { + logger.error("检查节点压力出错, cluster:{}", cluster, t); + } finally { + ThreadUtils.sleep(5000); + } + } + + } + + /** + * 一些神龙节点被回收,不再存在 + */ + private void removeNoLongerExistNode() { + Map tempMap = shenlongNodeOverloadMap; + Set keySet = tempMap.keySet(); + for (String key : keySet) { + if (!shenlongNodeResMap.containsKey(key)) { + shenlongNodeOverloadMap.remove(key); + } + } + } + + @Override + public void setCluster(String cluster) { + this.cluster = cluster; + } + + @Override + public boolean canScheduleNormalPod(int timeLimit) { + return shenlongPodRequestCpuTotal < shenlongNodeRequestCpuThreshold; + } + + /** + * 暂时只有超时的情况,不可调度。 此处针对把普通pod调度到神龙节点。 + * + * @return + */ + @Override + public List getNoSchedulableNodes() { + return new ArrayList<>(shenlongNodeOverloadMap.keySet()); + } + + /** + * 处理神龙pod调度策略 + * + * @param podCreateStrategy + */ + @Override + public void processPodCreateStrategy(PodCreateStrategy podCreateStrategy, String containers) { + boolean shenlong = containers.contains(appConfig.getShenlongImageCharacter());// 可在上一层处理 + if (!shenlong) { + return; + } + + List list = new ArrayList<>(shenlongNodeResMap.values()); + if (list.size() <= 1) { + return; + } + Collections.sort(list, new Comparator() { + @Override + public int compare(NodeRes o1, NodeRes o2) { + return o1.getRequestCpuRate().compareTo(o2.getRequestCpuRate()); + } + }); + + // bridge规模扩大时,必要改为集中分配 + double requestCpu = ContainerUtil.getRequestCpu(containers); + List noList = new ArrayList<>(); + for (int i = list.size() - 1; i > 0; i--) { + NodeRes higher = list.get(i); + NodeRes least = list.get(0); + + double unAffinityCpu = requestCpu + higher.getUnAffinityCpu(); + // allocatableCpu 应该取较大值。 + // k8s是整体调度的,可能把很多神龙pod调度到资源较小的节点,普通pod调度到资源较大的节点。这时候更加需要把神龙pod往资源较大节点分配。 + // 例如把3个神龙pod分配到8c节点,32c节点一个神龙pod都没有。 + double allocatableCpu = Math.max(higher.getAllocatableCpu(), least.getAllocatableCpu()); + // 当前是2台bridge节点,每台可以调节50%的量, 再乘0.8是因为pod本来倾向于分配到较小节点 + if (allocatableCpu * (higher.getRequestCpuRate() - least.getRequestCpuRate()) * 0.5 * 0.8 > unAffinityCpu) { + noList.add(higher.getNodeName()); + higher.addUnAffinityCpu(requestCpu); + } + } + + // 此处是针对神龙 pod + podCreateStrategy.setNoSchedulableShenlongNodes(noList); + + } + + @Override + public boolean processCreatePodResult(CreatePodResult result) { + String nodeName = K8sUtils.getNodeName(result.getPod()); + if (StringUtils.isBlank(nodeName)) { + return false;// 调度失败,连节点都没有 + } + + if (!shenlongNodeResMap.containsKey(nodeName)) { // 不是神龙节点 + return false; + } + + if (Boolean.FALSE.equals(result.getSuccess()) + && result.getMillisecondCost() >= sysConfigService.getCreatePodCostMaxTime()) { + shenlongNodeOverloadMap.put(nodeName, + System.currentTimeMillis() + sysConfigService.getShenlongNodeOverloadExpireTime()); +// k8sService.updateNodeLabel(cluster, nodeName, "type", BridgeNodeCsts.NODE_LABEL_TYPE_UNDER_PRESSURE); + logger.info("创建pod {} 超时 {}s,神龙节点{}超时状态开始, cluster:{}", + K8sUtils.getPodName(result.getPod()), result.getMillisecondCost() / 1000, nodeName, cluster); + } + + return true; + + } + + @Override + public void stop() { + terminated = true; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/BridgePod.java b/common/src/main/java/com/imitate/common/k8s/pojo/BridgePod.java new file mode 100644 index 0000000..93c79f9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/BridgePod.java @@ -0,0 +1,67 @@ +package com.imitate.common.k8s.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + + + +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "bridge_pod") +public class BridgePod extends AbstractDO { + private String name; + private String tpiID; + private String buildID; + + private String uid; + + private LocalDateTime k8sCreateTime; + private LocalDateTime deleteTime; + + private String secKey; + private LocalDateTime requestTime; + private Double pull; + private Double createPod; + private Double execute; + private Double evaluateAllTime; + + private String nodeName; + private String nodeIp; + + private String downloadStatus; + private String createPodStatus; + + public static final String CREATE_POD_STATUS_SUCCESS = "1"; + + public static final String CREATE_POD_STATUS_FAIL = "0"; + + private String compileStatus; + private String runStatus; + + public static final String RUN_STATUS_FAIL = "-1"; + public static final String RUN_STATUS_SUCCESS = "0"; + public static final String RUN_STATUS_EXEC_FAIL = "1"; + public static final String RUN_STATUS_TIMEOUT = "2"; + public static final String RUN_STATUS_OTHERS_FAIL = "3"; + + private String status; + + private String imageName; + + private String imageVersion; + + private Double cpuLimit; + + private Integer memoryLimit; + + private Double cpuRequest; + + private Integer memoryRequest; + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/ErrorPodInfo.java b/common/src/main/java/com/imitate/common/k8s/pojo/ErrorPodInfo.java new file mode 100644 index 0000000..5de2eb4 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/ErrorPodInfo.java @@ -0,0 +1,40 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * @author huqifeng + * @Time 2021-8-9 + * @Description pod创建超时后保存的错误pod信息 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "error_pod_info") +public class ErrorPodInfo extends AbstractDO { + private LocalDateTime errorTime; + private String podName; + private String nodeIp; + private String tpiID; + private String buildID; + private String podPhase; + private String podStatusReason; + private String podStatusMessage; + private String podConditionType; + private String podConditionStatus; + private String podConditionReason; + private String podConditionMessage; + private String containerStatusReady; + private String containerStatusWaiting; + private String containerStatusWaitingReason; + private String containerStatusWaitingMessage; + private String containerStatusTerminated; + private String containerStatusTerminatedReason; + private String containerStatusTerminatedMessage; + private String imageExist; + +} diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/EvaDayStat.java b/common/src/main/java/com/imitate/common/k8s/pojo/EvaDayStat.java new file mode 100644 index 0000000..3215ec9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/EvaDayStat.java @@ -0,0 +1,74 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; + +/** + * 评测每日统计 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "eva_day_stat") +public class EvaDayStat extends AbstractDO { + + + /** + * 统计日期,如20200101 + */ + private Integer statDate; + + /** + * 评测总数 + */ + private Integer evaTotal; + + /** + * 代码下载大于5秒数 + */ + private Integer pullSlow; + + /** + * 代码下载失败数 + */ + private Integer pullFail; + + /** + * 创建pod大于5秒数 + */ + private Integer createPodSlow; + + /** + * 创建pod失败数 + */ + private Integer createPodFail; + + /** + * 评测执行错误 + */ + private Integer execFail; + + /** + * 其它错误 + */ + private Integer othersFail; + + /** + * 评测错误比率: (execFail+othersFail)/evaTotal + */ + private Double evaFailRatio; + + /** + * 评测超时数 + */ + private Integer evaTimeout; + + /** + * 评测超时比例 + */ + private Double evaTimeoutRatio; + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/OjEvaDayStat.java b/common/src/main/java/com/imitate/common/k8s/pojo/OjEvaDayStat.java new file mode 100644 index 0000000..1336ffb --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/OjEvaDayStat.java @@ -0,0 +1,56 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; + +/** + * oj评测每日统计 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "oj_eva_day_stat") +public class OjEvaDayStat extends AbstractDO { + + + /** + * 统计日期,如20200101 + */ + private Integer statDate; + + /** + * 评测总数 + */ + private Integer evaTotal; + + /** + * 评测错误 + */ + private Integer evaFail; + + /** + * 评测错误率 + */ + private Double evaFailRatio; + + /** + * 评测超时 + */ + private Integer evaTimeout; + + /** + * 评测超时率 + */ + private Double evaTimeoutRatio; + + public OjEvaDayStat(Integer statDate, Integer evaTotal, Integer evaFail, Double evaFailRatio, Integer evaTimeout, Double evaTimeoutRatio) { + this.statDate = statDate; + this.evaTotal = evaTotal; + this.evaFail = evaFail; + this.evaFailRatio = evaFailRatio; + this.evaTimeout = evaTimeout; + this.evaTimeoutRatio = evaTimeoutRatio; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/PlatformConfig.java b/common/src/main/java/com/imitate/common/k8s/pojo/PlatformConfig.java new file mode 100644 index 0000000..bcece9c --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/PlatformConfig.java @@ -0,0 +1,29 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + + +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "platform_config") +public class PlatformConfig extends AbstractDO { + + private String platform; + + private String containers; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + private String script; + + private String fileNameSuffix; + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/RunPod.java b/common/src/main/java/com/imitate/common/k8s/pojo/RunPod.java new file mode 100644 index 0000000..386bc2d --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/RunPod.java @@ -0,0 +1,78 @@ +package com.imitate.common.k8s.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + + +@Data +@Table(name = "run_pod") +public class RunPod extends AbstractDO { + + + private String name; + + private String cluster; + + private String imageName; + + private Double cpuLimit; + + private Integer memoryLimit; + + private Double cpuRequest; + + private Integer memoryRequest; + + private Integer runPodType; + + private String nodeIp; + + private Long priority; + + private String createBy; + + private String svcPort; + + private String sshPort; + + public static final Long RUN_POD_PRIORITY_DEFAULT = 0L; + + private LocalDateTime expireTime; + + + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((super.getId() == null) ? 0 : super.getId().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RunPod other = (RunPod) obj; + if (super.getId() == null) { + if (other.getId() != null) + return false; + } else if (!super.getId().equals(other.getId())) + return false; + return true; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/SecurityContextConfig.java b/common/src/main/java/com/imitate/common/k8s/pojo/SecurityContextConfig.java new file mode 100644 index 0000000..1503f08 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/SecurityContextConfig.java @@ -0,0 +1,30 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * @author zmr + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "security_context_config") +public class SecurityContextConfig extends AbstractDO { + + private String imageName; + + private String cluster; + + private Boolean privileged; + + private String addCap; + + private String dropCap; + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/pojo/WindowsInfo.java b/common/src/main/java/com/imitate/common/k8s/pojo/WindowsInfo.java new file mode 100644 index 0000000..0055b60 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/pojo/WindowsInfo.java @@ -0,0 +1,44 @@ +package com.imitate.common.k8s.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * windows实例信息 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "windows_info") +public class WindowsInfo extends AbstractDO { + + + private String uniqId; + + private String instanceId; + + private String userID; + + private String templateName; + + private String port; + + private String vncPort; + + private String forwardTableId; + + private String forwardEntryId; + + private String vncForwardEntryId; + + private LocalDateTime autoReleaseTime; + + private Integer status; + + +} + + diff --git a/common/src/main/java/com/imitate/common/k8s/service/BridgeNodeService.java b/common/src/main/java/com/imitate/common/k8s/service/BridgeNodeService.java new file mode 100644 index 0000000..3c1212f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/BridgeNodeService.java @@ -0,0 +1,71 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.bean.BridgeNode; +import com.imitate.common.k8s.bean.NodeQueryParam; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.NodeAddress; +import io.fabric8.kubernetes.api.model.Pod; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class BridgeNodeService { + + @Autowired + private K8sService k8sService; + + public List getBridgePods(NodeQueryParam param) { + Map> nodePodMap = this.getNodePods(param.getCluster()); + + List nodeList = k8sService.getNodes(param); + + List list = new ArrayList<>(nodeList.size()); + for (Node node : nodeList) { + BridgeNode bn = new BridgeNode(); + bn.setName(node.getMetadata().getName()); + // ip + List addrs = node.getStatus().getAddresses(); + for (NodeAddress na : addrs) { + if ("InternalIP".equals(na.getType())) { + bn.setIp(na.getAddress()); + break; + } + } + // 创建时间 + String createTime = node.getMetadata().getCreationTimestamp(); + if (createTime.endsWith("Z")) { + createTime = createTime.substring(0, createTime.length() - 1); + } + bn.setCreateTime(LocalDateTime.parse(createTime)); + // pod数量 + List podList = nodePodMap.get(bn.getIp()); + bn.setPodNum(podList == null ? 0 : podList.size()); + + list.add(bn); + } + return list; + + } + + private Map> getNodePods(final String cluster) { + Map> map = new HashMap<>(); + List list = k8sService.getPods(cluster); + for (Pod pod : list) { + String hostIp = pod.getStatus().getHostIP(); + List podList = map.get(hostIp); + if (podList == null) { + podList = new ArrayList<>(); + map.put(hostIp, podList); + } + podList.add(pod); + } + return map; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/BridgePodService.java b/common/src/main/java/com/imitate/common/k8s/service/BridgePodService.java new file mode 100644 index 0000000..06c8914 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/BridgePodService.java @@ -0,0 +1,119 @@ +package com.imitate.common.k8s.service; + +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.PageHelper; +import com.imitate.common.k8s.bean.*; +import com.imitate.common.k8s.constant.BridgePodCsts; +import com.imitate.common.k8s.mapper.BridgePodMapper; +import com.imitate.common.k8s.pojo.EvaDayStat; +import com.imitate.common.k8s.pojo.*; +import com.imitate.common.util.TpUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Service +public class BridgePodService { + + @Autowired + private BridgePodMapper bridgePodMapper; + + public List getBridgePods(PodQueryParam param) { + + PageHelper.startPage(param.getPageNum(), param.getPageSize()); + return bridgePodMapper.selectBridgePod(param); + } + + public BridgePod createBridgePod(String name, String tpiID, String buildID, LocalDateTime requestTime, String secKey) { + BridgePod bridgePod = new BridgePod(); + bridgePod.setName(name); + bridgePod.setTpiID(tpiID); + bridgePod.setBuildID(buildID); + bridgePod.setSecKey(secKey); + bridgePod.setRequestTime(requestTime); + bridgePod.setStatus(BridgePodCsts.STATUS_BEGIN); + + bridgePodMapper.insert(bridgePod); + return bridgePod; + } + + public void updateBridgeInfo(JSONObject buildParams, TimeCost timeCost, BuildResult result) { + BridgePod bridgePod = new BridgePod(); + bridgePod.setId(buildParams.getLong("bridgePodId")); + String podName = buildParams.getString("podName"); + bridgePod.setName(podName); + bridgePod.setNodeName(buildParams.getString("nodeName")); + bridgePod.setNodeIp(buildParams.getString("nodeIp")); + + // image info + JSONObject mainContainer = buildParams.getJSONObject("mainContainer"); + bridgePod.setImageName(mainContainer.getString("imageName")); + bridgePod.setImageVersion(mainContainer.getString("imageVersion")); + bridgePod.setCpuLimit(Double.parseDouble(mainContainer.getString("cpuLimit"))); + Integer memoryLimit = Integer.parseInt(mainContainer.getString("memoryLimit").replace("M", "")); + bridgePod.setMemoryLimit(memoryLimit); + bridgePod.setCpuRequest(Double.parseDouble(mainContainer.getString("cpuRequest"))); + Integer memoryRequest = Integer.parseInt(mainContainer.getString("memoryRequest").replace("M", "")); + bridgePod.setMemoryRequest(memoryRequest); + + // 下载代码和创建pod + String pullTimeStr = timeCost.getPull(); + Double pullTime = StringUtils.isNotEmpty(pullTimeStr) ? Double.parseDouble(pullTimeStr) : null; + bridgePod.setPull(pullTime); + + String createPodStr = timeCost.getCreatePod(); + Double createPod = StringUtils.isNotEmpty(createPodStr) ? Double.parseDouble(createPodStr) : null; + bridgePod.setCreatePod(createPod); + + String createTime = buildParams.getString("k8sCreateTime"); + if (StringUtils.isNotEmpty(createTime)) { + if (createTime.endsWith("Z")) { + createTime = createTime.substring(0, createTime.length() - 1); + } + LocalDateTime time = LocalDateTime.parse(createTime); + // 转化为东八区时间 + if (time.isBefore(LocalDateTime.now().plusHours(1))) { + time.plusHours(8); + } + bridgePod.setK8sCreateTime(time); + + String uid = time.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + "-" + TpUtils.getTpiID(podName); + bridgePod.setUid(uid); + } + + // 评测时间数据 + String execute = timeCost.getExecute(); + Double takeTime = execute == null ? null : Double.parseDouble(execute); + bridgePod.setExecute(takeTime); + + bridgePod.setEvaluateAllTime(Double.parseDouble(timeCost.getEvaluateAllTime())); + + bridgePod.setDownloadStatus(result.getDownloadStatus()); + bridgePod.setCreatePodStatus(result.getCreatePodStatus()); + bridgePod.setCompileStatus(result.getCompileSuccess()); + String evaFailStatus = buildParams.getString("evaFailStatus"); + if (StringUtils.isNotEmpty(evaFailStatus)) { + bridgePod.setRunStatus(evaFailStatus); + } else { + bridgePod.setRunStatus(result.getStatus()); + } + bridgePod.setStatus(BridgePodCsts.STATUS_END); + + bridgePodMapper.updateByPrimaryKeySelective(bridgePod); + } + + public void deleteHisBridgePod(LocalDateTime deleteTime) { + bridgePodMapper.deleteByRequestTime(deleteTime); + } + + public EvaDayStat doEvaDayStat(Integer date) { + return bridgePodMapper.countEvaDayStat(date); + } + + public List getNodeEvaErrorInfos(LocalDateTime requestTime) { + return bridgePodMapper.selectNodeEvaErrorInfos(requestTime); + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/DiskService.java b/common/src/main/java/com/imitate/common/k8s/service/DiskService.java new file mode 100644 index 0000000..6c24486 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/DiskService.java @@ -0,0 +1,157 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.sys.settings.AppConfig; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; + +/** + * 磁盘相关service + * + * @author 威少 + */ +@Service +public class DiskService { + + @Autowired + private AppConfig appConfig; + + /** + * 文件类型实际输出所在文件夹名 + */ + public static final String TEST_CASE_ACTUAL_OUT_FILE_FOLDER = "filecase"; + /** + * 平台通用文件所在文件夹名 + */ + public static final String PLATFORM_FILE_FOLDER = "platform"; + /** + * 保护文件所在文件夹名 + */ + public static final String PROTECT_FILE_FOLDER = "protect"; + /** + * 私密版本库所在文件夹名 + */ + public static final String SECRET_FILE_FOLDER = "secret"; + /** + * TPI工作目录文件夹名前缀 + */ + public static final String TPI_WORKSPACE_FOLDER_PREFIX = "myshixun_"; + + /** + * 获取工作目录在bridge节点挂载目录的名称 + */ + public String getWorkspaceHostPath(String tpiID) { + // 分盘逻辑,对磁盘个数取余,使落到对应的盘 + String[] workspaceNFSConfigs = getWorkspaceNFSConfigs(); + int workspacesSize = workspaceNFSConfigs.length; + return workspaceNFSConfigs[(int)(NumberUtils.toLong(tpiID, 0) % workspacesSize)]; + } + + /** + * 对pod名字取hashcode取余决定盘 + */ + public String getWorkspaceHostPathByPodName(String podName) { + String[] workspaceNFSConfigs = getWorkspaceNFSConfigs(); + int index = (int)(podName.charAt(podName.length() - 1)) % workspaceNFSConfigs.length; + return workspaceNFSConfigs[index]; + } + + /** + * 获取TPI在bridge节点的工作目录 + */ + public String getTpiWorkspaceHostPath(String tpiID) { + String workspace = getWorkspaceHostPath(tpiID); + return workspace + File.separator + TPI_WORKSPACE_FOLDER_PREFIX + tpiID; + } + + + /** + * 获取TPI 测试用例在bridge节点的host path + */ + public String getTpiTestCaseActualOutHostPath(String tpiID) { + String testCaseActualOutHostPath = getWorkspaceHostPath(tpiID) + File.separator + TEST_CASE_ACTUAL_OUT_FILE_FOLDER; + return StringUtils.isEmpty(tpiID) ? testCaseActualOutHostPath : testCaseActualOutHostPath + File.separator + tpiID; + } + + /** + * 获取私密版本库工作目录 + */ + public String getTpiSecretWorkspace(String tpiID) { + return getWorkspaceHostPath(tpiID) + File.separator + SECRET_FILE_FOLDER + File.separator + TPI_WORKSPACE_FOLDER_PREFIX + tpiID + File.separator + TpCsts.TP_UNIFY_REPO_NAME; + } + + // ------------------------ 所有build的方法用给定的目录作为基础目录 ------------------------ + /** + * 构建数据集存放路径 + */ + public String buildTpDataHostPath(String basePath, String identifier) { + return basePath + File.separator + identifier; + } + + /** + * 构建平台固定脚本等存放的路径 + */ + public String buildTpiPlatformHostPath(String workspaceHostPath) { + return workspaceHostPath + File.separator + PLATFORM_FILE_FOLDER + + File.separator + "eva"; + } + + /** + * 构建工作目录 + */ + public String buildTpiWorkspace(String workspace, String tpiID) { + return workspace + File.separator + TPI_WORKSPACE_FOLDER_PREFIX + tpiID; + } + + /** + * 构建保护工作空间, 存放评测执行脚本等 + */ + public String buildTpiProtectSpace(String workspace, String tpiID) { + return workspace + File.separator + PROTECT_FILE_FOLDER + File.separator + TPI_WORKSPACE_FOLDER_PREFIX + tpiID; + } + + /** + * 构建TPI版本库路径 + */ + public String buildTpiRepoPath(String tpiWorkspace, String tpiRepoName) { + return tpiWorkspace + File.separator + tpiRepoName; + } + + /** + * 构建私密版本库路径 + */ + public String buildSecretRepoSpace(String tpiRepoPath, String secretDir) { + return tpiRepoPath + File.separator + secretDir; + } + + /** + * 构建TPM测试用例目录 + */ + public String buildTpmTestCaseHostPath(String testCaseHostPath, String tpmIdentifier) { + if (StringUtils.isEmpty(tpmIdentifier)) { + return testCaseHostPath; + } + return testCaseHostPath + File.separator + tpmIdentifier; + } + + /** + * 构建TPI测试用例的容器内挂载路径 + */ + public String buildTpiTestCaseActualOutMountPath(String workspace) { + return workspace + File.separator + TEST_CASE_ACTUAL_OUT_FILE_FOLDER; + } + + /** + * 取所有工作目录盘 + */ + public String[] getWorkspaceNFSConfigs() { + String workspaceNFSConfig = appConfig.getWorkspaceNFSConfigs(); + return workspaceNFSConfig.split(","); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/EvaDayStatService.java b/common/src/main/java/com/imitate/common/k8s/service/EvaDayStatService.java new file mode 100644 index 0000000..dcbbbe1 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/EvaDayStatService.java @@ -0,0 +1,19 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.mapper.EvaDayStatMapper; +import com.imitate.common.k8s.pojo.EvaDayStat; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EvaDayStatService { + + @Autowired + private EvaDayStatMapper evaDayStatMapper; + + public void insert(EvaDayStat evaDayStat) { + evaDayStatMapper.insertSelective(evaDayStat); + } + +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/K8sService.java b/common/src/main/java/com/imitate/common/k8s/service/K8sService.java new file mode 100644 index 0000000..828a1bc --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/K8sService.java @@ -0,0 +1,1330 @@ +package com.imitate.common.k8s.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.*; +import com.imitate.common.k8s.mapper.ErrorPodInfoMapper; +import com.imitate.common.k8s.pojo.*; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.k8s.util.K8sClientUtil; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.ShellUtil; +import com.imitate.common.util.ThreadUtils; +import com.imitate.common.util.net.TelnetUtils; +import io.fabric8.kubernetes.api.model.AffinityFluent.NodeAffinityNested; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.PodFluent.SpecNested; +import io.fabric8.kubernetes.api.model.PodSpecFluent.AffinityNested; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; +import io.fabric8.kubernetes.api.model.autoscaling.v2beta2.HorizontalPodAutoscaler; +import io.fabric8.kubernetes.api.model.autoscaling.v2beta2.HorizontalPodAutoscalerBuilder; +import io.fabric8.kubernetes.api.model.autoscaling.v2beta2.MetricSpec; +import io.fabric8.kubernetes.api.model.autoscaling.v2beta2.MetricSpecBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.io.File; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static com.imitate.common.sys.constant.SysConfigCsts.*; + + +@Service +public class K8sService { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * 节点是否正在pull某个镜像 + */ + private volatile ConcurrentHashMap pullImageConcurrentHashMap = new ConcurrentHashMap<>(); + + @Autowired + private AppConfig appConfig; + + @Autowired + private RunPodService runPodService; + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private SecurityContextService securityContextService; + + @Autowired + private ErrorPodInfoMapper errorPodInfoMapper; + + @Autowired + private DiskService diskService; + + /** + * 获取k8s集群中各node的ip + * + * @return + */ + public List getNodesIP(final String cluster) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List addressList = new ArrayList(); + List nodeList = client.nodes().list().getItems(); + for (Node node : nodeList) { + addressList.add(node.getStatus().getAddresses().get(0).getAddress()); + } + return addressList; + } + + /** + * 获取k8s集群中Node列表 + * + * @return + */ + public List getNodes(NodeQueryParam param) { + KubernetesClient client = K8sClientUtil.getClient(param.getCluster()); + NonNamespaceOperation> operation = client.nodes(); + + if (param.getName() != null) { + Node node = operation.withName(param.getName()).get(); + if (node == null) { + return Collections.emptyList(); + } else { + return Arrays.asList(node); + } + } else if (param.getLabels() != null) { + return operation.withLabels(param.getLabels()).list().getItems(); + } else { + return operation.list().getItems(); + } + + } + + public Node getNode(String cluster, String nodeName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.nodes().withName(nodeName).get(); + } + + /** + * 获取某个节点上的Pod数量 + * + * @param nodeIP + * @return + */ + public int getPodNum(final String cluster, String nodeIP) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List pods = client.pods().list().getItems(); + if (pods == null) { + return 0; + } + int podNum = 0; + for (Pod pod : pods) { + if (nodeIP.equals(pod.getStatus().getHostIP())) { + podNum++; + } + } + return podNum; + } + + public int getPodSum(final String cluster) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List pods = client.pods().list().getItems(); + return pods == null ? 0 : pods.size(); + } + + public List getPods(final String cluster) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List pods = client.pods().list().getItems(); + return pods == null ? Collections.emptyList() : pods; + } + + public CreatePodResult createPod(final String cluster, String podName, String tpiID, String type, String containers, + PodCreateStrategy podCreateStrategy, Map envs, String identifier, Boolean createImage, String buildID) { + return createPod(cluster, podName, tpiID, type, containers, + podCreateStrategy, envs, identifier, createImage, buildID, null, false); + } + + /** + * 创pod + * + * @param podName + * @param tpiID + * @param type + * @param containers 形如"[{image:'gcc:v1.0',cpuLimit:'1',memoryLimit:'1024M'},{image:'gcc:v1.0',cpuLimit:'1',memoryLimit:'1024M'}]" + * @return + */ + public CreatePodResult createPod(final String cluster, String podName, String tpiID, String type, String containers, + PodCreateStrategy podCreateStrategy, Map envs, + String identifier, Boolean createImage, String buildID, String tpmIdentifier, boolean mountTestCaseDir) { + logger.info("[{}] 开始创建pod {} ", tpiID, podName); + boolean needShenlong = containers.contains(appConfig.getShenlongImageCharacter()); + boolean needGpu = containers.contains(appConfig.getGpuImage()); + + // 定义pod的labels + Map labels = new HashMap<>(3); + labels.put("name", podName); + labels.put("tpiID", tpiID); + labels.put("type", type); + if (needShenlong) { + labels.put(TpCsts.NODE_REQUIRE, appConfig.getShenlongImageCharacter()); + } else if (needGpu) { + labels.put(TpCsts.NODE_REQUIRE, appConfig.getGpuImage()); + } + + List vList = new ArrayList<>(); + // 平台脚本宿主机路径 + HostPathVolumeSource platformVolumeSource = new HostPathVolumeSource( + diskService.buildTpiPlatformHostPath(diskService.getWorkspaceHostPath(tpiID)), null); + // 平台脚本挂载路径 + VolumeMount platformVolumeMount = new VolumeMount(); + platformVolumeMount.setMountPath(TpCsts.TPI_PLATFORM_MOUNT_PATH); + platformVolumeMount.setName("platform"); + platformVolumeMount.setReadOnly(Boolean.TRUE); + vList.add(platformVolumeMount); + + // 保护空间宿主机路径 + String psHostPath = diskService.buildTpiProtectSpace(diskService.getWorkspaceHostPath(tpiID), tpiID); + // 保护空间挂载路径 + HostPathVolumeSource psHostPathVolumeSource = new HostPathVolumeSource(psHostPath, null); + VolumeMount psVolumeMount = new VolumeMount(); + psVolumeMount.setMountPath(TpCsts.TPI_PROTECT_SPACE_MOUNT_PATH); + psVolumeMount.setName("protectspace"); + psVolumeMount.setReadOnly(Boolean.TRUE); + vList.add(psVolumeMount); + + // vnc类型pod设置shm大小 + if (TpCsts.TYPE_VNC.equals(type)) { + VolumeMount cacheVolumeMount = new VolumeMount(); + cacheVolumeMount.setMountPath(TpCsts.VNC_POD_DEV_SHM_PATH); + cacheVolumeMount.setName("cache-volume"); + vList.add(cacheVolumeMount); + } + + // 工作空间挂载路径 + VolumeMount wsVolumeMount = new VolumeMount(); + if (type.startsWith(TpCsts.TYPE_JUPYTER)) { + wsVolumeMount.setMountPath(diskService.buildTpiWorkspace(appConfig.getWorkspace(), tpiID)); // jupyter pod挂载路径应当与宿主机解耦 + } else { + wsVolumeMount.setMountPath(appConfig.getWorkspace()); + } + wsVolumeMount.setName("workspace"); + vList.add(wsVolumeMount); + + // 数据集宿主机路径 + String dataDir = appConfig.getDatadir(); + String bigDataDir = appConfig.getBigDataDir(); + String dataHostPath = diskService.buildTpDataHostPath(dataDir, identifier); + String bigDataHostPath = diskService.buildTpDataHostPath(bigDataDir, identifier); + HostPathVolumeSource dataVolumeSource = new HostPathVolumeSource(dataHostPath, null); + HostPathVolumeSource bigDataVolumeSource = new HostPathVolumeSource(bigDataHostPath, null); + if (StringUtils.isNotBlank(identifier)) { + if (new File(dataHostPath).exists()) { + VolumeMount dataVolumeMount = new VolumeMount(); + dataVolumeMount.setMountPath(dataDir); + dataVolumeMount.setName("datadir"); + vList.add(dataVolumeMount); + } + if (new File(bigDataHostPath).exists()) { + VolumeMount bigDataVolumeMount = new VolumeMount(); + bigDataVolumeMount.setMountPath(bigDataDir); + bigDataVolumeMount.setName("bigdatadir"); + vList.add(bigDataVolumeMount); + } + } + + // 测试集输入、预期输出、 实际输出 + HostPathVolumeSource testCaseVolumeSource = null; + HostPathVolumeSource testCaseActualOutVolumeSource = null; + if (mountTestCaseDir) { + String testCaseDir = appConfig.getTestCaseDir(); + String testCaseHostPath = diskService.buildTpmTestCaseHostPath(testCaseDir, tpmIdentifier); + testCaseVolumeSource = new HostPathVolumeSource(testCaseHostPath, null); + VolumeMount testCaseVolumeMount = new VolumeMount(); + testCaseVolumeMount.setMountPath(testCaseDir); + testCaseVolumeMount.setName("test-case-dir"); + testCaseVolumeMount.setReadOnly(true); + vList.add(testCaseVolumeMount); + + String tpiTestCaseActualOutMountPath = diskService.buildTpiTestCaseActualOutMountPath(appConfig.getWorkspace()); + String tpiTestCaseActualOutHostPath = diskService.getTpiTestCaseActualOutHostPath(tpiID); + testCaseActualOutVolumeSource = new HostPathVolumeSource(tpiTestCaseActualOutHostPath, null); + VolumeMount testCaseActualOutVolumeMount = new VolumeMount(); + testCaseActualOutVolumeMount.setMountPath(tpiTestCaseActualOutMountPath); + testCaseActualOutVolumeMount.setName("test-case-actual-out-dir"); + vList.add(testCaseActualOutVolumeMount); + } + + + Quantity diskQuantity = null; + // pod的所有容器(主类别+子类别) + JSONArray jsonArray = JSONArray.parseArray(containers); + List containerList = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + Container container = new Container(); + JSONObject props = jsonArray.getJSONObject(i); + String image = props.getString("image"); + String[] imageInfo = image.split(":"); + String imageName = imageInfo[0]; + container.setName(imageName); + labels.putIfAbsent("image", imageName); + // 用户自定义镜像处理 + if (createImage != null && createImage && JedisUtil.exists(TpCsts.CREATE_IMAGE_NAME_REDIS_KEY + tpiID)) { + image = sysConfigService.getK8sImagePullUrl() + JedisUtil.get(TpCsts.CREATE_IMAGE_NAME_REDIS_KEY + tpiID); + } + container.setImage(image); + container.setImagePullPolicy("IfNotPresent"); + + // 定义pod的securityContext + SecurityContextConfig securityContextConfig = securityContextService.getSecurityContextConfig(cluster, imageName); + if (securityContextConfig != null) { + SecurityContext securityContext = new SecurityContext(); + if (securityContextConfig.getPrivileged()) { + securityContext.setPrivileged(true); + } + securityContext.setReadOnlyRootFilesystem(Boolean.FALSE); + String addCap = securityContextConfig.getAddCap(); + Capabilities capabilities = new Capabilities(); + if (StringUtils.isNotEmpty(addCap)) { + capabilities.setAdd(Arrays.asList(addCap.split(","))); + } + String dropCap = securityContextConfig.getDropCap(); + if (StringUtils.isNotEmpty(dropCap)) { + capabilities.setDrop(Arrays.asList(dropCap.split(","))); + } + securityContext.setCapabilities(capabilities); + container.setSecurityContext(securityContext); + } + + // 定义container env + setContainerEnvs(type, envs, container); + + // 资源 + Map limits = new HashMap<>(2); + limits.put("cpu", new Quantity(props.getString("cpuLimit"))); + limits.put("memory", new Quantity(props.getString("memoryLimit"))); + + String resourceLimit = props.getString("resourceLimit"); + if (StringUtils.isNotEmpty(resourceLimit)) { + diskQuantity = new Quantity(props.getString("resourceLimit")); + } + + // 最低资源限制 + Map requests = new HashMap<>(2); + requests.put("cpu", new Quantity(props.getString("cpuRequest"))); + requests.put("memory", new Quantity(props.getString("memoryRequest"))); + + ResourceRequirements resourceRequirements = new ResourceRequirements(limits, requests); + container.setResources(resourceRequirements); + container.setVolumeMounts(vList); + + // 次类别设置启动脚本为不包含ssh启动命令的脚本 + String cmdParams = props.getString("cmd_params"); + if ("sub".equals(props.getString("type"))) { + container.setCommand(StringUtils.isNotEmpty(cmdParams) ? Arrays.asList(cmdParams.split(",")) : Collections.singletonList("/start2.sh")); + containerList.add(container); + } else { + if (StringUtils.isNotEmpty(cmdParams)) { + container.setCommand(Arrays.asList(cmdParams.split(","))); + } + setPostStartExec(container); + containerList.add(0, container); + } + } + + Pod pod = null; + KubernetesClient client = K8sClientUtil.getClient(cluster); + + Map nodeSelectMap = new HashMap<>(); + if (sysConfigService.getOpenNodeStatusCheck()) { + // nodeSelectMap.put(TpCsts.K8S_NODE_STATUS_LABEL_VALUE, TpCsts.K8S_NODE_STATUS_READY); + } + String nodeSelectMapKey = sysConfigService.getImageNodeSelectKey(containers); + if (StringUtils.isNotEmpty(nodeSelectMapKey)) { + nodeSelectMap.put(nodeSelectMapKey, "true"); + } else { + nodeSelectMap.put("type", "others");// 普通实训 + } + if (createImage != null && createImage) { + nodeSelectMap.put("forevernode", "true");// 永久节点 + } + + // 创建pod + SpecNested specNested = new PodBuilder().withNewMetadata().withName(podName).addToLabels(labels).endMetadata().withNewSpec() + .addToNodeSelector(nodeSelectMap); + NodeAffinityNested>> nodeAffintyNested = specNested.withNewAffinity() + .withNewNodeAffinity(); + + if (podCreateStrategy.getAutoscaleNodeWeight() > 0) { + nodeAffintyNested = nodeAffintyNested.addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(podCreateStrategy.getAutoscaleNodeWeight()) + .withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder() + .withKey(TpCsts.K8S_ELASTIC_NODE_LABEL_KEY).withOperator("DoesNotExist").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } + + Integer preReduceNodeWeight = sysConfigService.getPreReduceNodeWeight(); + if (JedisUtil.exists(TpCsts.K8S_PRE_REDUCE_NODE_REDIS_KEY) && preReduceNodeWeight != null && preReduceNodeWeight > 0) { + nodeAffintyNested = nodeAffintyNested.addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(preReduceNodeWeight) + .withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder() + .withKey(TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY).withOperator("DoesNotExist").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } + + Integer newCapacityNodeWeight = sysConfigService.getNewCapacityNodeWeight(); + if (JedisUtil.exists(TpCsts.K8S_NEW_CAPACITY_NODE_REDIS_KEY) && newCapacityNodeWeight != null && newCapacityNodeWeight > 0) { + String createPodFlowControl = sysConfigService.getCreatePodFlowControl(); + String flowControlTarget = String.valueOf(System.currentTimeMillis()); + if (StringUtils.isNotEmpty(createPodFlowControl) && createPodFlowControl.contains(flowControlTarget.substring(flowControlTarget.length() - 1))) { + nodeAffintyNested = nodeAffintyNested.addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(newCapacityNodeWeight) + .withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder() + .withKey(TpCsts.K8S_NEW_CAPACITY_NODE_LABEL_KEY).withOperator("DoesNotExist").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } else { + podCreateStrategy.setTimeLimit(20); + } + } + + // 有些GPU节点分配的GPU pod 资源过多,减少分配 + List noSchedulableGpuNodes = podCreateStrategy.getNoSchedulableGpuNodes(); + // 有些神龙节点分配的神龙pod 资源过多,减少分配 + List noSchedulableShenlongNodes = podCreateStrategy.getNoSchedulableShenlongNodes(); + if (needShenlong && noSchedulableShenlongNodes != null && noSchedulableShenlongNodes.size() > 0) { + nodeAffintyNested = nodeAffintyNested + .addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(podCreateStrategy.getPodNoSchedulableWeight()).withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder().withKey("kubernetes.io/hostname") + .withValues(noSchedulableShenlongNodes) + .withOperator("NotIn").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } else if (needGpu && noSchedulableGpuNodes != null && noSchedulableGpuNodes.size() > 0) { + nodeAffintyNested = nodeAffintyNested.addNewPreferredDuringSchedulingIgnoredDuringExecution() + .withWeight(podCreateStrategy.getPodNoSchedulableWeight()).withNewPreference() + .withMatchExpressions( + Arrays.asList(new NodeSelectorRequirementBuilder().withKey("kubernetes.io/hostname") + .withValues(noSchedulableGpuNodes).withOperator("NotIn").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } + + + List noSchedulableNodes = podCreateStrategy.getNoSchedulableNodes(); + if (noSchedulableNodes != null && noSchedulableNodes.size() > 0) { + nodeAffintyNested = nodeAffintyNested + .addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(podCreateStrategy.getPodNoSchedulableWeight()).withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder().withKey("kubernetes.io/hostname") + .withValues(noSchedulableNodes) + .withOperator("NotIn").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + } + + specNested = nodeAffintyNested.endNodeAffinity().endAffinity(); + + // 当为shenlong实训或shenlong节点可分配普通实训pod时,增加该pod的污点容忍 + if (needShenlong || Boolean.TRUE.equals(podCreateStrategy.getNormalPodCanShenlong())) { + Toleration toleration = new Toleration(); + toleration.setEffect("NoSchedule"); + toleration.setKey("shenlong"); + toleration.setOperator("Exists"); + specNested = specNested.addToTolerations(toleration); + } + // 当为GPU实训或GPU节点可分配普通实训pod时,增加该pod的污点容忍 + if (needGpu || Boolean.TRUE.equals(podCreateStrategy.getNormalPodCanGpu())) { + Toleration toleration = new Toleration(); + toleration.setEffect("NoSchedule"); + toleration.setKey("GPU"); + toleration.setOperator("Exists"); + specNested = specNested.addToTolerations(toleration); + } + // 当该pod可调度到oj节点时,增加该pod的污点容忍 + if (Boolean.TRUE.equals(podCreateStrategy.getNormalPodCanOj())) { + Toleration toleration = new Toleration(); + toleration.setEffect("NoSchedule"); + toleration.setKey("oj"); + toleration.setOperator("Exists"); + specNested = specNested.addToTolerations(toleration); + } + + // 容器与 volume + specNested = specNested.withContainers(containerList); + specNested = specNested.addNewVolume().withName("platform").withHostPath(platformVolumeSource).endVolume(); + specNested = specNested.addNewVolume().withName("protectspace").withHostPath(psHostPathVolumeSource) + .endVolume(); + + HostPathVolumeSource wsHostPathVolumeSource = new HostPathVolumeSource(diskService.getTpiWorkspaceHostPath(tpiID), null); + specNested = specNested.addNewVolume().withName("workspace").withHostPath(wsHostPathVolumeSource).endVolume(); + + if (TpCsts.TYPE_VNC.equals(type)) { + EmptyDirVolumeSource emptyDirVolumeSource = new EmptyDirVolumeSource("Memory", new Quantity("512Mi")); + specNested = specNested.addNewVolume().withName("cache-volume").withEmptyDir(emptyDirVolumeSource).endVolume(); + } + if (StringUtils.isNotBlank(identifier)) { + if (new File(dataHostPath).exists()) { + specNested = specNested.addNewVolume().withName("datadir").withHostPath(dataVolumeSource).endVolume(); + } + if (new File(bigDataHostPath).exists()) { + specNested = specNested.addNewVolume().withName("bigdatadir").withHostPath(bigDataVolumeSource).endVolume(); + } + } + // 测试集输入、预期输出、实际输出 + if (mountTestCaseDir) { + specNested = specNested.addNewVolume().withName("test-case-dir").withHostPath(testCaseVolumeSource).endVolume(); + specNested = specNested.addNewVolume().withName("test-case-actual-out-dir").withHostPath(testCaseActualOutVolumeSource).endVolume(); + } + + if (createImage != null && createImage) { + specNested = specNested.addNewImagePullSecret(sysConfigService.getK8sImagePullSecret()); + } + client.pods().inNamespace("default").createOrReplace(specNested.endSpec().build()); + + // 等待pod创建完成达到Running可用状态 + long start = System.currentTimeMillis(); + CreatePodResult createPodResult = new CreatePodResult(start); + pod = client.pods().inNamespace("default").withName(podName).get(); + if (pod == null) { + logger.error("创建pod {} 失败, 原因未知, 可能是 FailedCreatePodSandBox", podName); + return createPodResult; + } + String nodeIp = null; + String nodeName = null; + int redoTime = sysConfigService.getCreatePodErrorRetryCount(); + while (pod != null && !K8sUtils.isPodRunning(pod)) { + String phase = pod.getStatus().getPhase(); + if (StringUtils.isEmpty(nodeIp)) { + nodeIp = pod.getStatus().getHostIP(); + } + if (StringUtils.isEmpty(nodeName)) { + nodeName = pod.getSpec().getNodeName(); + createPodResult.setNodeName(nodeName); + } + if (this.isTimeout(start, podCreateStrategy, createImage)) { + + //测试pod创建失败原因,之后需删除[start] + ErrorPodInfo errorPodInfo = new ErrorPodInfo(); + PodStatus podStatus = pod.getStatus(); + List imagesNotExist = K8sUtils.podImagesNotExist(pod); + K8sUtils.createErrorPodInfo(errorPodInfo, podStatus, podName, nodeIp, tpiID, buildID, CollectionUtils.isEmpty(imagesNotExist)); + errorPodInfoMapper.insert(errorPodInfo); + //测试pod创建失败原因,之后需删除[end] + + //判断创建pod失败是否是由服务器上无所需镜像所造成的 + if (!CollectionUtils.isEmpty(imagesNotExist)) { + createPodResult.setPod(pod); + createPodResult.setStatus(CreatePodResult.CREATE_POD_STATUS_FAIL_PULL_IMAGE); + runPodService.deleteRunPodAfterTime(podName, 0); + logger.error("创建pod {} 失败 image not exists, nodeIp: {}, phase: {}", podName, nodeIp, phase); + // 拉取镜像 + dockerPull(nodeIp, imagesNotExist); + return createPodResult; + } + + String nodeNames = JedisUtil.get(TpCsts.K8S_NEW_CAPACITY_NODE_REDIS_KEY); + if (redoTime > 0 && StringUtils.isNotEmpty(nodeName) && StringUtils.isNotEmpty(nodeNames) && nodeNames.contains(nodeName)) { + logger.warn("创建pod {}超时{},重新创建, nodeIp: {}, phase: {}", podName, podCreateStrategy.getTimeLimit(), nodeIp, phase); + redoTime--; + deletePodSync(cluster, podName); + if (noSchedulableNodes == null) { + noSchedulableNodes = new ArrayList<>(); + } + noSchedulableNodes.addAll(Arrays.asList(nodeNames.split(","))); + nodeAffintyNested = nodeAffintyNested + .addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(podCreateStrategy.getPodNoSchedulableWeight()).withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder().withKey("kubernetes.io/hostname") + .withValues(noSchedulableNodes) + .withOperator("NotIn").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + specNested = nodeAffintyNested.endNodeAffinity().endAffinity(); + client.pods().inNamespace("default").createOrReplace(specNested.endSpec().build()); + + // 重置数据 + start = System.currentTimeMillis(); + nodeIp = null; + nodeName = null; + createPodResult.setNodeName(nodeName); + } else { + createPodResult.setPod(pod); + runPodService.deleteRunPodAfterTime(podName, 0); + logger.warn("创建pod {}超时{},删除pod, nodeIp: {}, phase: {}", podName, podCreateStrategy.getTimeLimit(), nodeIp, phase); + return createPodResult; + } + } + + ThreadUtils.sleep(1000); + pod = client.pods().inNamespace("default").withName(podName).get(); + if (redoTime > 0 && pod == null) { + String nodeNames = JedisUtil.get(TpCsts.K8S_NEW_CAPACITY_NODE_REDIS_KEY); + if (StringUtils.isNotEmpty(nodeName) && StringUtils.isNotEmpty(nodeNames) && nodeNames.contains(nodeName)) { + logger.warn("创建pod {}失败{},重新创建, nodeIp: {}, phase: {}", podName, podCreateStrategy.getTimeLimit(), nodeIp, phase); + redoTime--; + if (noSchedulableNodes == null) { + noSchedulableNodes = new ArrayList<>(); + } + noSchedulableNodes.addAll(Arrays.asList(nodeNames.split(","))); + nodeAffintyNested = nodeAffintyNested + .addNewPreferredDuringSchedulingIgnoredDuringExecution().withWeight(podCreateStrategy.getPodNoSchedulableWeight()).withNewPreference() + .withMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder().withKey("kubernetes.io/hostname") + .withValues(noSchedulableNodes) + .withOperator("NotIn").build())) + .endPreference().endPreferredDuringSchedulingIgnoredDuringExecution(); + specNested = nodeAffintyNested.endNodeAffinity().endAffinity(); + client.pods().inNamespace("default").createOrReplace(specNested.endSpec().build()); + + pod = client.pods().inNamespace("default").withName(podName).get(); + + // 重置数据 + start = System.currentTimeMillis(); + nodeIp = null; + nodeName = null; + createPodResult.setNodeName(nodeName); + } + } + } + + if (pod == null) { + // 删除run_pod记录 + if (StringUtils.isNotEmpty(JedisUtil.get(RunPodService.DELETE_POD_REDIS_KEY + podName))) { + createPodResult.setStatus(CreatePodResult.CREATE_POD_STATUS_FAIL_DELETE_NOW); + } + runPodService.deleteRunPodAfterTime(podName, 0); + logger.error("创建pod {} 失败, 原因未知, nodeIp: {}", podName, nodeIp); + } else { + createPodResult.setNodeName(pod.getSpec().getNodeName()); + createPodResult.setPod(pod); + createPodResult.setSuccess(Boolean.TRUE); + nodeIp = pod.getStatus().getHostIP(); + logger.info("创建pod成功,podName: {}, nodeIp: {}, 耗时: {}s", podName, nodeIp, createPodResult.getMillisecondCost() / 1000); + + + diskLimit(clusterConfigService.getKubeConfig(cluster), podName, diskQuantity); + } + return createPodResult; + } + + private void setContainerEnvs(String type, Map envs, Container container) { + if (envs == null) { + // 防止从评测进来创建的容器没有标识密码的环境变量 + if (TpCsts.TYPE_WEBSSH.equals(type) || TpCsts.TYPE_EVASSH.equals(type)) { + envs = Collections.singletonMap(WEBSSH_PW_ENV , RandomStringUtils.randomAlphanumeric(16)); + } else if (TpCsts.TYPE_VNC.equals(type)) { + envs = Collections.singletonMap(VNC_PW_ENV, RandomStringUtils.randomAlphanumeric(16)); + } + } + if (envs != null) { + container.setEnv(convertMapToEnvVars(envs)); + } + } + + /** + * Map 转环境变量 + */ + private List convertMapToEnvVars(Map envs) { + List envVars = new ArrayList<>(); + envs.forEach((key, value) -> { + EnvVar envVar = new EnvVar(); + envVar.setName(key); + envVar.setValue(value); + envVars.add(envVar); + }); + return envVars; + } + + + /** + * 隐藏容器的nfs挂载信息 + * 修改webssh密码 + * + * @param container 容器镜像 + */ + private void setPostStartExec(Container container) { + String command = "echo started "; + + String hideNFSInfoCommand = sysConfigService.getHideNFSInfoCommand(); + if (StringUtils.isNotEmpty(hideNFSInfoCommand)) { + command += " && " + hideNFSInfoCommand; + } + + EnvVar websshPwd = container.getEnv().stream().filter(envVar -> WEBSSH_PW_ENV.equals(envVar.getName())).findAny().orElse(null); + if (websshPwd != null) { + command += " && echo root:" + websshPwd.getValue() + "|chpasswd"; + } + ContainerUtil.setLifecycleStartCommand(container, Arrays.asList("/bin/sh", "-c", command)); + } + + private boolean isTimeout(long start, PodCreateStrategy podCreateStrategy, Boolean createImage) { + if (podCreateStrategy == null || podCreateStrategy.getTimeLimit() == null) { + return false; + } + long now = System.currentTimeMillis(); + long diff = (now - start) / 1000; + Integer timeLimit = podCreateStrategy.getTimeLimit(); + if (createImage != null && createImage) { + timeLimit = timeLimit * 2; + } + if (diff > timeLimit) { + return true; + } + return false; + } + + private boolean isTimeout(long start, int timeLimit) { + long now = System.currentTimeMillis(); + long diff = (now - start) / 1000; + if (diff > timeLimit) { + return true; + } + return false; + } + + public void createService(final String cluster, String name, String podName, String tpiID, Integer port, + Integer nodePort) { + + logger.info("开始创建 service {}, nodePort: {}", name, nodePort); + // 定义service的labels + Map labels = new HashMap<>(); + labels.put("name", name); + labels.put("tpiID", tpiID); + labels.put("port", String.valueOf(port)); + + Map selector = new HashMap<>(); + selector.put("name", podName); + + ServicePort servicePort = new ServicePort(); + servicePort.setPort(port); + servicePort.setNodePort(nodePort); + KubernetesClient client = K8sClientUtil.getClient(cluster); + try { + client.services().createOrReplace(new ServiceBuilder().withNewMetadata().withName(name).addToLabels(labels).endMetadata() + .withNewSpec().withSelector(selector).withType("NodePort").withPorts(servicePort).endSpec().build()); + } catch (KubernetesClientException e) { + logger.info(e.getMessage()); + } + } + + /** + * 创service + * + * @param name + * @param tpiID + * @param port + * @param nodePort + */ + public void createService(final String cluster, String name, String tpiID, Integer port, Integer nodePort) { + createService(cluster, name, name, tpiID, port, nodePort); + } + + /** + * 获取符合筛选条件的pod + * + * @param inLabels 在标签内 + * @return + */ + public String getPod(final String cluster, Map inLabels) { + // 加上所有key in values的筛选条件 + KubernetesClient client = K8sClientUtil.getClient(cluster); + FilterWatchListDeletable filteredPodList = null; + for (Map.Entry label : inLabels.entrySet()) { + if (filteredPodList == null) { + filteredPodList = client.pods().inNamespace("default").withLabelIn(label.getKey(), label.getValue()); + } + filteredPodList = filteredPodList.withLabelIn(label.getKey(), label.getValue()); + } + List pods = filteredPodList.list().getItems(); + // 在目前的需求里边都只有一个对应的pod,所以取第0个元素 + if (pods != null && pods.size() > 0) { + Pod pod = pods.get(0); + return pod.getMetadata().getName(); + } else { + logger.warn("pod不存在! 声明拥有的标签:{}", JSON.toJSONString(inLabels)); + return ""; + } + } + + public boolean svcExist(final String cluster, String podName) { + io.fabric8.kubernetes.api.model.Service service = this.getService(cluster, podName); + return (service != null); + } + + public io.fabric8.kubernetes.api.model.Service getService(final String cluster, String serviceName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.services().inNamespace("default").withName(serviceName).get(); + } + + public List getServices(final String cluster) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List services = client.services().inNamespace("default").list().getItems(); + return services == null ? Collections.emptyList() : services; + } + + public List getServicesByTpiID(String cluster, String tpiID) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + List services = client.services().inNamespace("default").withLabel(TpCsts.TPI_ID, tpiID).list().getItems(); + return services == null ? Collections.emptyList() : services; + } + + public Boolean deleteService(final String cluster, String serviceName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.services().inNamespace("default").withName(serviceName).delete(); + } + + public Boolean deleteServiceByTpiId(final String cluster, String tpiID) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.services().inNamespace("default").withLabel(TpCsts.TPI_ID, tpiID).delete(); + } + + public Pod getPod(final String cluster, String podName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.pods().inNamespace("default").withName(podName).get(); + } + + public Pod getRunningPod(final String cluster, String podName, int timeLimit) { + long now = System.currentTimeMillis(); + KubernetesClient client = K8sClientUtil.getClient(cluster); + Pod pod = client.pods().inNamespace("default").withName(podName).get(); + while (pod != null) { + // 如果处于Pending或者ContainerCreating的状态,是需要等待的 + String phase = pod.getStatus().getPhase(); + if ("Pending".equals(phase) || "ContainerCreating".equals(phase)) { + if (isTimeout(now, timeLimit)) { + logger.warn("pod长时间处于 Pending 或 ContainerCreating 状态,将删除pod! podName: {}", podName); + deletePod(cluster, podName); + return null; + } + } else if (K8sUtils.isPodRunning(pod)) { + // Running状态返回 + return pod; + } else { + logger.warn("pod状态异常,将删除pod! podName: {}", podName); + deletePod(cluster, podName); + return null; + } + ThreadUtils.sleep(1000); + pod = client.pods().inNamespace("default").withName(podName).get(); + } + logger.info("pod不存在! podName:{}", podName); + return null; + } + + /** + * 立刻删除pod + * + * @param podName + */ + public Boolean deletePod(final String cluster, String podName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.pods().inNamespace("default").withName(podName).withGracePeriod(0).delete(); + } + + /** + * 打印pod详情 + * + * @param cluster + * @param podName + */ + public void describePod(final String cluster, String podName) { + // 删除之前获取pod状态详细信息 + String cmd = "kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " describe pod " + podName; + ShellResult describeResult = ShellUtil.executeAndGetExitStatus(cmd); + logger.info("current pod desc info, out: {}", describeResult.getOut()); + } + + /** + * 根据pod标签立刻批量删除pod + * + * @param cluster + * @param labelKey + * @param labelValue + * @return + */ + public Boolean deletePodsWithLabel(final String cluster, String labelKey, String labelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.pods().inNamespace("default").withLabel(labelKey, labelValue).withGracePeriod(0).delete(); + } + + /** + * 同步删除pod + * + * @param podName + */ + public Boolean deletePodSync(final String cluster, String podName) { + // 同步删除 + String cmd = "kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " delete pods " + podName + + " --now=true --wait=true --timeout=30s"; + ShellResult delExist = ShellUtil.executeAndGetExitStatus(cmd); + if (delExist.getExitStatus() != 0) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public List getNodeListWithLabel(final String cluster, String labelKey, String labelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.nodes().withLabel(labelKey, labelValue).list().getItems(); + } + + public List getNodeNameListWithLabel(final String cluster, String labelKey, String labelValue) { + List nodes = getNodeListWithLabel(cluster, labelKey, labelValue); + List nodeNames = new ArrayList<>(nodes.size()); + for (Node node : nodes) { + nodeNames.add(K8sUtils.getNodeName(node)); + } + return nodeNames; + } + + public List getPodListWithNodeName(final String cluster, String nodeName) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.pods().inNamespace("default").withField("spec.nodeName", nodeName).list().getItems(); + } + + /** + * 反向查找pod列表。反向是要求不存在指定的label + */ + public List getPodListReversely(final String cluster, String nodeName, String withoutLabelKey, String withoutLabelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + FilterWatchListDeletable fwld = client.pods().inNamespace("default") + .withField("spec.nodeName", nodeName).withoutLabel(withoutLabelKey, withoutLabelValue); + return fwld.list().getItems(); + } + + public List getPodList(final String cluster, String labelKey, String labelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.pods().inNamespace("default").withLabel(labelKey, labelValue).list().getItems(); + } + + public List getPodList(final String cluster, List inLabels, List notInLabels, Map withLabels) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + FilterWatchListDeletable t = client.pods().inNamespace(TpCsts.DEFAULT_NAMESPACE); + if (!CollectionUtils.isEmpty(withLabels)) { + t = t.withLabels(withLabels); + } + for (String label : inLabels) { + t = t.withLabel(label); + } + for (String label : notInLabels) { + t = t.withoutLabel(label); + } + + return t.list().getItems(); + } + + /** + * 创建检测pod。检测node是否能够及时、成功地创建pod。 + */ + public Boolean createCheckPod(final String cluster, String podName, String nodeName, int timeLimit, String containerImage) { + + Container container = new Container(); + container.setName(containerImage.split(":")[0]); + container.setImage(containerImage); + container.setImagePullPolicy("IfNotPresent"); + + Pod pod = null; + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.pods().inNamespace("default").createOrReplace(new PodBuilder().withNewMetadata().withName(podName).endMetadata() + .withNewSpec().withNodeName(nodeName).withContainers(container).endSpec().build()); + + // 等待pod创建完成达到Running可用状态 + final long start = System.currentTimeMillis(); + pod = client.pods().inNamespace("default").withName(podName).get(); + if (pod == null) { + return Boolean.FALSE; + } + while (pod != null && !"Running".equals(pod.getStatus().getPhase())) { + if (this.isTimeout(start, timeLimit)) { + return Boolean.FALSE; + } + + ThreadUtils.sleep(1000); + pod = client.pods().inNamespace("default").withName(podName).get(); + } + + if (pod == null) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public void updateNodeLabel(final String cluster, String nodeName, String labelKey, String labelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + + client.nodes().withName(nodeName).edit(n -> new NodeBuilder(n) + .editMetadata() + .addToLabels(labelKey, labelValue) + .endMetadata() + .build()); + } + + + public void removeNodeLabel(String cluster, String nodeName, String labelKey) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.nodes().withName(nodeName).edit(n -> new NodeBuilder(n) + .editMetadata() + .removeFromLabels(labelKey) + .endMetadata() + .build()); + } + + public void removePodLabel(String cluster, String podName, String labelKey) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.pods().withName(podName).edit(p -> new PodBuilder(p) + .editMetadata() + .removeFromLabels(labelKey) + .endMetadata() + .build()); + } + + public void updatePodLabel(final String cluster, String podName, String labelKey, String labelValue) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.pods().withName(podName).edit(p -> new PodBuilder(p) + .editMetadata() + .addToLabels(labelKey, labelValue) + .endMetadata() + .build()); + } + + public void addNodeTaint(final String cluster, String nodeName, String effect, String key, String value) { + Taint taint = new Taint(); + taint.setEffect(effect); + taint.setKey(key); + taint.setValue(value); + + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.nodes().withName(nodeName).edit(n -> new NodeBuilder(n) + .editSpec() + .addToTaints(new Taint[]{taint}) + .endSpec() + .build()); + } + + public void removeNodeTaint(final String cluster, String nodeName, String key) { + Taint taint = new Taint(); + taint.setKey(key); + + KubernetesClient client = K8sClientUtil.getClient(cluster); + client.nodes().withName(nodeName).edit(n -> new NodeBuilder(n) + .editSpec() + .removeFromTaints(new Taint[]{taint}) + .endSpec() + .build()); + } + + /** + * 检测端口是否可用 + * + * @param hostIp 节点IP + * @param nodePort 端口 + * @param timeout 超时时间 + * @return 是否可用 + */ + public boolean checkPortConnection(String hostIp, int nodePort, int timeout) { + Boolean connect = Boolean.FALSE; + try { + TelnetUtils.connect(hostIp, nodePort, 3); + connect = Boolean.TRUE; + logger.info("检查服务telnet成功, address: {}, port: {}, timeout:{}", hostIp, nodePort, timeout); + } catch (Exception ignored) { + } + return connect; + } + + + /** + * 为节点拉取镜像 + * + * @param nodeIp 节点ip + * @param imageNameList 镜像名称列表 + */ + public void dockerPull(String nodeIp, List imageNameList) { + for (String imageName : imageNameList) { + // 加锁执行pull + String syncKey = nodeIp + imageName; + if (!pullImageConcurrentHashMap.getOrDefault(syncKey, false)) { + pullImageConcurrentHashMap.put(syncKey, true); + + // 登录到节点执行pull脚本 + String remoteConnection = String.format(SysConfigCsts.REMOTE_CONN_PREFIX_DEF, nodeIp); + String pullCommand = remoteConnection + " pull.sh " + imageName; + ShellResult result = ShellUtil.executeAndGetExitStatus(pullCommand); + if (0 != result.getExitStatus()) { + logger.error("{} images {} not exists, pull failed, e: {}", nodeIp, imageName, result.getOut()); + } else { + logger.warn("{} images {} not exists, pull success", nodeIp, imageName); + } + + // 释放锁 + pullImageConcurrentHashMap.put(syncKey, false); + } + } + } + + + /** + * 磁盘限制 + * + * @param config + * @param podName + * @param quantity + */ + public void diskLimit(String config, String podName, Quantity quantity) { + // 没传不限制 + if (quantity == null) { + return; + } + long limitSize = getLimit(quantity.getFormat()) * Long.valueOf(quantity.getAmount()); + + // Disk limit + if (limitSize <= 0) { + return; + } + String diskLimitCommand = String.format("kubectl --kubeconfig=%s exec %s -- bash -c 'echo -e \"ulimit -f %d\\n\" >> /root/.bashrc && source /root/.bashrc'", config, podName, limitSize); + String result = ShellUtil.execute(diskLimitCommand); + logger.info("执行限制磁盘命令返回:{}", result); + } + + // 默认kb为单位 + public long getLimit(String format) { + switch (format) { + case "M": + return 1024; + case "G": + return 1024 * 1024; + default: + return 1; + } + } + + /** + * 创建pod + */ + public Pod createPod(String cluster, PodCreateParams podCreateParams) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + + Pod pod = new PodBuilder() + .withNewMetadata() + .withName(podCreateParams.getName()) + .addToLabels(podCreateParams.getLabels()) + .endMetadata() + .withNewSpec() + .withTerminationGracePeriodSeconds(300L) + .addToNodeSelector(podCreateParams.getNodeSelector()) + .withContainers(podCreateParams.getContainerList()) + .addToVolumes(podCreateParams.getVolumes()) + .endSpec() + .build(); + + return client.pods().inNamespace(TpCsts.DEFAULT_NAMESPACE).createOrReplace(pod); + } + + /** + * 创建deployment + */ + public Deployment createDeployment(String cluster, DeploymentCreateParams deploymentCreateParams) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + + Deployment deployment = new DeploymentBuilder() + .withNewMetadata() + .withName(deploymentCreateParams.getName()) + .endMetadata() + .withNewSpec() + .withReplicas(deploymentCreateParams.getReplicas()) + .withNewTemplate() + .withNewMetadata() + .addToLabels(deploymentCreateParams.getPodCreateParams().getLabels()) + .endMetadata() + .withNewSpec() + .withTerminationGracePeriodSeconds(300L) + .addToNodeSelector(deploymentCreateParams.getPodCreateParams().getNodeSelector()) + .withContainers(deploymentCreateParams.getPodCreateParams().getContainerList()) + .addToVolumes(deploymentCreateParams.getPodCreateParams().getVolumes()) + .endSpec() + .endTemplate() + .withNewSelector() + .addToMatchLabels(deploymentCreateParams.getPodCreateParams().getLabels()) + .endSelector() + .endSpec() + .build(); + + return client.apps().deployments().create(deployment); + + } + + /** + * 创建HPA + */ + public HorizontalPodAutoscaler createHPA(String cluster, HPACreateParams hpaCreateParams) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + + // 阔缩容指标为CPU和内存利用率 + MetricSpec cpuMetricSpec = new MetricSpecBuilder() + .withType("Resource") + .withNewResource() + .withName("cpu") + .withNewTarget() + .withType("Utilization") + .withAverageUtilization(hpaCreateParams.getDesireCpuPercent()) + .endTarget() + .endResource() + .build(); + MetricSpec memoryMetricSpec = new MetricSpecBuilder() + .withType("Resource") + .withNewResource() + .withName("memory") + .withNewTarget() + .withType("Utilization") + .withAverageUtilization(hpaCreateParams.getDesireMemoryPercent()) + .endTarget() + .endResource() + .build(); + + // 定义&创建HPA + HorizontalPodAutoscaler horizontalPodAutoscaler = new HorizontalPodAutoscalerBuilder() + .withNewMetadata().withName(hpaCreateParams.getName()).endMetadata() + .withNewSpec() + .withNewScaleTargetRef() + .withApiVersion("apps/v1") + .withKind("Deployment") + .withName(hpaCreateParams.getDeploymentName()) + .endScaleTargetRef() + .withMinReplicas(hpaCreateParams.getMinReplicas()) + .withMaxReplicas(hpaCreateParams.getMaxReplicas()) + .addToMetrics(cpuMetricSpec) + .addToMetrics(memoryMetricSpec) + .endSpec() + .build(); + + return client.autoscaling().v2beta2().horizontalPodAutoscalers().create(horizontalPodAutoscaler); + + } + + /** + * 获取HPA + */ + public HorizontalPodAutoscaler getHPA(String cluster, String name) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.autoscaling().v2beta2().horizontalPodAutoscalers().withName(name).get(); + } + + /** + * 获取Deployment + */ + public Deployment getDeployment(String cluster, String name) { + KubernetesClient client = K8sClientUtil.getClient(cluster); + return client.apps().deployments().withName(name).get(); + } + + /** + * 容器创建参数 -> 容器对象 + */ + public Container getContainer(ContainerCreateParams containerCreateParams) { + Container container = new Container(); + + // metadata + container.setName(containerCreateParams.getImage().split(":")[0]); + container.setImage(containerCreateParams.getImage()); + container.setImagePullPolicy(containerCreateParams.getImagePullPolicy()); + + // securityContext + SecurityContext securityContext = new SecurityContext(); + securityContext.setPrivileged(true); + container.setSecurityContext(securityContext); + + // resources + // resource limit + Map limits = new HashMap<>(2); + if (StringUtils.isNotEmpty(containerCreateParams.getMemoryLimit())) { + limits.put("memory", new Quantity(containerCreateParams.getMemoryLimit())); + } + if (StringUtils.isNotEmpty(containerCreateParams.getCpuLimit())) { + limits.put("cpu", new Quantity(containerCreateParams.getCpuLimit())); + } + // resource request + Map requests = new HashMap<>(2); + if (StringUtils.isNotEmpty(containerCreateParams.getMemoryRequest())) { + requests.put("memory", new Quantity(containerCreateParams.getMemoryRequest())); + } + if (StringUtils.isNotEmpty(containerCreateParams.getCpuRequest())) { + requests.put("cpu", new Quantity(containerCreateParams.getCpuRequest())); + } + ResourceRequirements resourceRequirements = new ResourceRequirements(limits, requests); + container.setResources(resourceRequirements); + + // volume + List volumeMountList = getContainerMount(containerCreateParams.getContainerMountParams()); + container.setVolumeMounts(volumeMountList); + + // command + if (!CollectionUtils.isEmpty(containerCreateParams.getCommand())) { + container.setCommand(containerCreateParams.getCommand()); + } + + // postStartCommand + if (!CollectionUtils.isEmpty(containerCreateParams.getPostStartCommand())) { + Lifecycle lifecycle = ObjectUtils.firstNonNull(container.getLifecycle(), new Lifecycle()); + Handler handler = new Handler(); + ExecAction execAction = new ExecAction(); + execAction.setCommand(containerCreateParams.getPostStartCommand()); + handler.setExec(execAction); + lifecycle.setPostStart(handler); + container.setLifecycle(lifecycle); + } + + // prestopCommand + if (!CollectionUtils.isEmpty(containerCreateParams.getPrestopCommand())) { + Lifecycle lifecycle = ObjectUtils.firstNonNull(container.getLifecycle(), new Lifecycle()); + Handler handler = new Handler(); + ExecAction execAction = new ExecAction(); + execAction.setCommand(containerCreateParams.getPrestopCommand()); + handler.setExec(execAction); + lifecycle.setPreStop(handler); + container.setLifecycle(lifecycle); + } + + return container; + + } + + /** + * 获取容器数据卷 + */ + public List getContainerMount(List mountParamsList) { + List vList = new ArrayList<>(); + + // 挂载路径 + for (ContainerMountParams mountParams : mountParamsList) { + VolumeMount platformVolumeMount = new VolumeMount(); + platformVolumeMount.setMountPath(mountParams.getPath()); + platformVolumeMount.setName(mountParams.getName()); + platformVolumeMount.setReadOnly(mountParams.getReadonly()); + vList.add(platformVolumeMount); + } + + return vList; + } + + /** + * 获取宿主机数据卷 + */ + public Volume getHostPathVolume(String name, String hostPath) { + return new VolumeBuilder().withName(name).withHostPath( + new HostPathVolumeSourceBuilder().withPath(hostPath).build() + ).build(); + } + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/NodeResUsageService.java b/common/src/main/java/com/imitate/common/k8s/service/NodeResUsageService.java new file mode 100644 index 0000000..ff75e01 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/NodeResUsageService.java @@ -0,0 +1,120 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.util.ShellUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * k8s节点资源使用信息服务 + */ +@Component +public class NodeResUsageService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Map> + private volatile Map> nodeCpuUsageRateMap = new HashMap<>(); + + @Autowired + private ClusterManager clusterManager; + + public void refreshNodeResUsage() { + Map> tmpMap = new HashMap<>(); + Map clusterMap = clusterManager.getClusterInfo(); + for (ClusterInfo cInfo : clusterMap.values()) { + String cluster = cInfo.getClusterConfig().getName(); + + // 加timeout避免某个集群网络缓慢等问题。可考虑集群各自管理 + String cmd = "timeout 2 kubectl --kubeconfig=" + cInfo.getClusterConfig().getKubeconfig() + + " top node | awk '{print $1,$2,$3,$4,$5}'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + if (result.getExitStatus() == 0) { + String resUsageOut = result.getOut(); + Map resUsageMap = getNewNodeResUsageMap(cluster, resUsageOut); + tmpMap.put(cluster, resUsageMap); + } + } + + nodeCpuUsageRateMap = tmpMap; + } + + public Integer getNodeCpuUsageRate(String cluster, String nodeName) { + + Map resUsageMap = nodeCpuUsageRateMap.get(cluster); + if (resUsageMap == null) { + logger.info("集群资源使用率尚未统计, cluster:{}, nodeName: {}", cluster, nodeName); + return TpCsts.NODE_CPU_USAGE_UNKNOWN; + } else { + Integer nodeCpuUsageRate = resUsageMap.get(nodeName); + if (nodeCpuUsageRate == null) { + logger.info("未获取到节点cpu使用率 , cluster:{}, nodeName: {}", cluster, nodeName); + return TpCsts.NODE_CPU_USAGE_UNKNOWN; + } + return nodeCpuUsageRate; + } + + } + + private Map getNewNodeResUsageMap(String cluster, String resUsageOut) { + Map newNodeResUsageMap = new HashMap<>(); + String[] nodeResUsages = resUsageOut.split(System.getProperty("line.separator")); + for (int i = 1; i < nodeResUsages.length; i++) { + String nodeResUsage = nodeResUsages[i]; + String[] infos = nodeResUsage.split(" "); + String nodeName = infos[0]; + String nodeCpuUsageStr = infos[2]; + String nodeMemoryUsageStr = infos[4]; + int nodeCpuUsage=0; + int nodeMemoryUsage; + // kubectl top 会偶尔出现获取资源数据为 的情况 + if (!"".equals(nodeCpuUsageStr)) { + try{ + nodeCpuUsage = Integer.parseInt(nodeCpuUsageStr.replace("%", "")); + //若k8s节点cpu使用率超过90%,钉钉报警 + if( nodeCpuUsage>90 ) + { + String message = "k8s节点:"+nodeName+",cpu使用率为:"+nodeCpuUsageStr+",服务器cpu繁忙"; + } + newNodeResUsageMap.put(nodeName, nodeCpuUsage); + } + catch ( Exception e ) + { + logger.error( "执行kubectl top node命令返回的结果中cpu数据不合法,nodeCpuUsage:{}",nodeCpuUsageStr,e); + } + if( !"".equals(nodeMemoryUsageStr) ) + { + try{ + nodeMemoryUsage = Integer.parseInt(nodeMemoryUsageStr.replace("%", "")); + //若k8s节点内存使用率超过90%,钉钉报警 + if( nodeMemoryUsage>90 ) + { + String message = "k8s节点:"+nodeName+",内存使用率为:"+nodeMemoryUsageStr+",服务器内存即将耗尽"; + } + } + catch ( Exception e ) + { + logger.error( "执行kubectl top node命令返回的结果中memory数据不合法,nodeMemoryUsage:{}",nodeMemoryUsageStr,e); + } + } + } else { + // 使用该节点上次获取到的cpu使用信息 + Map resUsageMap = nodeCpuUsageRateMap.get(cluster); + if (resUsageMap != null && resUsageMap.get(nodeName) != null) { + nodeCpuUsage = resUsageMap.get(nodeName); + newNodeResUsageMap.put(nodeName, nodeCpuUsage); + } + } + } + return newNodeResUsageMap; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/OjEvaDayStatService.java b/common/src/main/java/com/imitate/common/k8s/service/OjEvaDayStatService.java new file mode 100644 index 0000000..786d501 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/OjEvaDayStatService.java @@ -0,0 +1,54 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.mapper.OjEvaDayStatMapper; +import com.imitate.common.k8s.bean.BuildResult; +import com.imitate.common.k8s.pojo.OjEvaDayStat; +import com.imitate.common.util.JedisUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Service +public class OjEvaDayStatService { + + @Autowired + private OjEvaDayStatMapper ojEvaDayStatMapper; + + private static final String OJ_EVA_TOTAL_REDIS_KEY = "oj_total_"; + private static final String OJ_EVA_FAIL_REDIS_KEY = "oj_fail_"; + private static final String OJ_EVA_TIMEOUT_REDIS_KEY = "oj_timeout_"; + + public void ojEvaDayStat(Integer statDate) { + String totalStr = JedisUtil.get(OJ_EVA_TOTAL_REDIS_KEY + statDate); + Integer total = StringUtils.isEmpty(totalStr) ? 0 : Integer.parseInt(totalStr); + + String failStr = JedisUtil.get(OJ_EVA_FAIL_REDIS_KEY + statDate); + Integer fail = StringUtils.isEmpty(failStr) ? 0 : Integer.parseInt(failStr); + + String timeoutStr = JedisUtil.get(OJ_EVA_TIMEOUT_REDIS_KEY + statDate); + Integer timeout = StringUtils.isEmpty(timeoutStr) ? 0 : Integer.parseInt(timeoutStr); + + double failRatio = total == 0 ? 0 : fail / (double) total; + double timeoutRatio = total == 0 ? 0 : timeout / (double) total; + + OjEvaDayStat ojEvaDayStat = new OjEvaDayStat(statDate, total, fail, failRatio, timeout, timeoutRatio); + + ojEvaDayStatMapper.insertSelective(ojEvaDayStat); + } + + public void ojEvaStat(String status, boolean isCodeExecuteFail) { + Integer statDate = Integer.parseInt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); + JedisUtil.incrBy(OJ_EVA_TOTAL_REDIS_KEY + statDate, 1); + if (BuildResult.Status.RUN_TIMEOUT.getValue().equals(status)) { + JedisUtil.incrBy(OJ_EVA_TIMEOUT_REDIS_KEY + statDate, 1); + return; + } + if (BuildResult.Status.EXECUTE_ERROR.getValue().equals(status) && !isCodeExecuteFail) { + JedisUtil.incrBy(OJ_EVA_FAIL_REDIS_KEY + statDate, 1); + } + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/PlatformConfigService.java b/common/src/main/java/com/imitate/common/k8s/service/PlatformConfigService.java new file mode 100644 index 0000000..55882f4 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/PlatformConfigService.java @@ -0,0 +1,59 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.mapper.PlatformConfigMapper; +import com.imitate.common.k8s.pojo.PlatformConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 平台配置表 + */ +@Service +public class PlatformConfigService { + + private volatile static Map platformConfigMap = new HashMap<>(); + + + @Autowired + private PlatformConfigMapper platformConfigMapper; + + public void refreshPlatformConfig() { + Map tmpMap = new HashMap<>(); + List pcs = platformConfigMapper.selectAllConfig(); + for (PlatformConfig pc : pcs) { + String platform = pc.getPlatform(); + tmpMap.put(platform, pc); + } + + platformConfigMap = tmpMap; + } + + public PlatformConfig getPlatformConfig(String platform) { + PlatformConfig pc = platformConfigMap.get(platform); + if (pc == null) { + throw new RuntimeException("不支持的平台:" + platform); + } + return pc; + } + + public Set getAllPlatforms() { + return platformConfigMap.keySet(); + } + + public void savePlatformConfig(PlatformConfig pc) { + platformConfigMapper.insertSelective(pc); + } + + public void updatePlatformConfig(PlatformConfig pc) { + platformConfigMapper.updateByPrimaryKeySelective(pc); + } + + + +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/PortService.java b/common/src/main/java/com/imitate/common/k8s/service/PortService.java new file mode 100644 index 0000000..e814693 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/PortService.java @@ -0,0 +1,110 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.pojo.SysConfig; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.JedisUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class PortService { + private Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private K8sService k8sService; + + @Autowired + private AppConfig appConfig; + + @Transactional + public int allocatePort(String usablePortTypeKey,int k8sMinUsablePort,int k8sMaxUsablePort){ + long port = JedisUtil.incrBy(usablePortTypeKey, 1); + if (port < k8sMinUsablePort) { + return this.initPort(port, usablePortTypeKey,k8sMinUsablePort); + } + if (port >= k8sMaxUsablePort) { + logger.info("分配的port {} 达到上限 {},等待端口重置后重新分配", port, k8sMaxUsablePort); + + SysConfig configParam = new SysConfig(); + configParam.setName(SysConfigCsts.CONFIG_PORT_RESET); + configParam.setVal(appConfig.getBridgeInstanceName());// 暂时写死 + SysConfig sysConfig = null; + try { + sysConfig = sysConfigService.lockSysConfig(configParam); + if (sysConfig != null) {// 成功获得锁 + port = JedisUtil.incrBy(usablePortTypeKey, 1); + if (port >= k8sMaxUsablePort) { + logger.info("分配的port {} 达到上限 {},已经锁定端口重置锁,将重置端口到最小可用值", port, k8sMaxUsablePort); + JedisUtil.set(usablePortTypeKey, k8sMinUsablePort + ""); + port = JedisUtil.incrBy(usablePortTypeKey, 1); + } + } else {// 没有获得锁,那么是被其它任务锁定,并且重置了端口 + port = JedisUtil.incrBy(usablePortTypeKey, 1); + } + } catch (Exception e) { + logger.error("重置端口错误", e); + } finally { + if (sysConfig != null) { + sysConfigService.releasSysConfigLock(configParam); + } + } + + } + return (int) port; + } + + + + @Transactional + public int allocatePort(String usablePortTypeKey) { + int k8sMinUsablePort = sysConfigService.getK8sMinUsablePort(); + int k8sMaxUsablePort = sysConfigService.getK8sMaxUsablePort(); + return allocatePort(usablePortTypeKey,k8sMinUsablePort,k8sMaxUsablePort); + } + + + private int initPort(long port, String usablePortTypeKey,int k8sMinUsablePort) { + SysConfig configParam = new SysConfig(); + configParam.setName(SysConfigCsts.CONFIG_PORT_RESET); + configParam.setVal(appConfig.getBridgeInstanceName());// 暂时写死 + SysConfig sysConfig = null; + try { + sysConfig = sysConfigService.lockSysConfig(configParam); + if (sysConfig != null) {// 成功获得锁 + port = JedisUtil.incrBy(usablePortTypeKey, 1); + if (port < k8sMinUsablePort) { + logger.info("分配的port {} 小于最小值 {},已经锁定端口重置锁,将重置端口到最小可用值", port, k8sMinUsablePort); + JedisUtil.set(usablePortTypeKey, k8sMinUsablePort + ""); + port = JedisUtil.incrBy(usablePortTypeKey, 1); + } + } else {// 没有获得锁,那么是被其它任务锁定,并且重置了端口 + port = JedisUtil.incrBy(usablePortTypeKey, 1); + } + } catch (Exception e) { + logger.error("重置端口错误", e); + } finally { + if (sysConfig != null) { + sysConfigService.releasSysConfigLock(configParam); + } + } + + return (int) port; + } + + public Integer getServicePort(final String cluster, String podName) { + io.fabric8.kubernetes.api.model.Service service = k8sService.getService(cluster, podName); + if (service != null) { + return K8sUtils.getNodePort(service); + } + return null; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/RunPodService.java b/common/src/main/java/com/imitate/common/k8s/service/RunPodService.java new file mode 100644 index 0000000..71a8087 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/RunPodService.java @@ -0,0 +1,511 @@ +package com.imitate.common.k8s.service; + +import com.alibaba.fastjson.JSONObject; + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.bean.NodeResStat; +import com.imitate.common.k8s.bean.PodCreateStrategy; +import com.imitate.common.k8s.bean.RunPodQueryParam; +import com.imitate.common.k8s.mapper.RunPodMapper; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.NodeManager; +import com.imitate.common.k8s.pojo.*; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.k8s.util.K8sPodCommandExecutor; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.ThreadUtils; +import com.imitate.common.util.TpUtils; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.imitate.common.sys.constant.SysConfigCsts.*; + +@Service +public class RunPodService { + private Logger logger = LoggerFactory.getLogger(getClass()); + + public static final String DELETE_POD_REDIS_KEY = "delete_pod_key_"; + + public static final String DELETE_POD_REDIS_LOCK_KEY = "delete_pod_lock_key_"; + + @Autowired + private RunPodMapper runPodMapper; + + @Autowired + private K8sService k8sService; + + @Autowired + private AppConfig appConfig; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private NodeManager nodeManager; + + @Autowired + private ClusterManager clusterManager; + + public List getRunPods(RunPodQueryParam param) { + return runPodMapper.selectRunPods(param); + } + + /** + * 仅仅删除 RunPod记录 + * + * @param podName + * @return + */ + public void deleteRunPodRecordOnly(String podName) { + runPodMapper.deleteByName(podName); + } + + /** + * 如果更新成功返回RunPod,否则返回null + * + * @param podName + * @param delayTime + * @return + */ + public void deleteRunPodAfterTime(String podName, long delayTime) { + RunPod runPod = new RunPod(); + runPod.setName(podName); + LocalDateTime now = LocalDateTime.now(); + runPod.setUpdateTime(now); + LocalDateTime expireTime = now.plusSeconds(delayTime); + runPod.setExpireTime(expireTime); + runPodMapper.updateByNameSelective(runPod); + } + + /** + * 延长runPod过期时间,当新过期时间大于旧过期时间才更新 + * + * @param podName + * @param delayTime + * @return + */ + public boolean delayRunPodExpireTime(String podName, long delayTime) { + RunPod runPod = new RunPod(); + runPod.setName(podName); + LocalDateTime now = LocalDateTime.now(); + runPod.setUpdateTime(now); + LocalDateTime expireTime = now.plusSeconds(delayTime); + runPod.setExpireTime(expireTime); + int rowNum = runPodMapper.delayRunPodExpireTime(runPod); + return rowNum > 0; + } + + public boolean deteleRunPod(RunPod runPod) { + // 常驻镜像或pod不予删除 + if (sysConfigService.getLongLiveImage().contains(runPod.getImageName()) || sysConfigService.getLongLivePod().contains(runPod.getName())) { + return false; + } + + //加redis锁 + JedisUtil.setnx(DELETE_POD_REDIS_LOCK_KEY + runPod.getName(),runPod.getName()); + int r = runPodMapper.deleteByNameExpired(runPod.getName(), LocalDateTime.now()); + if(r > 0) { + try { + delK8sPod(runPod); + logger.info("pod 过期被删除,podName:{}", runPod.getName()); + } catch (Exception e) { + logger.error("k8s 删除pod异常,将继续删除run_pod, podName: {}, cluster: {}", runPod.getName(), runPod.getCluster(), e); + } + } + //删除redis锁,释放锁 + JedisUtil.del(DELETE_POD_REDIS_LOCK_KEY + runPod.getName()); + return r > 0; + } + + private void delK8sPod(RunPod runPod) { + String podName = runPod.getName(); + String tpiID = TpUtils.getTpiID(podName); + String cluster = runPod.getCluster(); + + JedisUtil.psetex(DELETE_POD_REDIS_KEY + podName, podName, sysConfigService.getDeletePodRedisExpireTime()); + + + + try { + // 根据podName来删除pod + Boolean r = k8sService.deletePod(cluster, podName); + logger.info("删除k8s pod {} 结果: {}", podName, r); + r = k8sService.deleteServiceByTpiId(cluster, tpiID); + logger.info("删除k8s service {} 结果: {}", tpiID, r); + } catch(Exception ignore) { + } + + + } + + public void deteleRunPodSync(final String cluster, String tpiId, String podName, Integer podType) { + // 必须是先删除记录,再删除pod;否则可能删除掉其它线程刚刚创建的记录,导致pod永远不会删除掉。 + // TODO 可以优化,先查询pod,后面按照时间删除,如果时间已经更新,则不再删除,否则删除。 + deleteRunPodRecordOnly(podName); + + // 查看pod是否已经存在 + Pod pod = k8sService.getPod(cluster, podName); + if (pod != null) { + String phase = pod.getStatus().getPhase(); + // 特殊状态的pod只能立即删除,否则无法删除掉。此时node可能已经宕机 + if ("Terminating".equals(phase) || "Unknown".equals(phase)) { + k8sService.deletePod(cluster, podName); + // 检查pod是否已成功删除,10秒内应该足够删除 + int times = 10; + while (times-- > 0) { + if (k8sService.getPod(cluster, podName) == null) { + break; + } + ThreadUtils.sleep(1000); + } + } else { + // 同步删除pod + k8sService.deletePodSync(cluster, podName); + } + } + k8sService.deleteServiceByTpiId(cluster, tpiId); + logger.info("同步删除run_pod {} 完成", podName); + } + + public CreatePodResult createRunPod(final String cluster, JSONObject buildParams, String tpiID, String type, + int createPodTimeLimit, int timeLimit, Boolean localNode, String bigDataFile) { + CreatePodResult createPodResult = new CreatePodResult(); + String podName = type + "-" + tpiID; + try { + Pod pod = null; + Integer podType = buildParams.getInteger("podType"); + if(delayRunPodExpireTime(podName, timeLimit + 60)) { + pod = k8sService.getRunningPod(cluster, podName, createPodTimeLimit); + } else { + RunPod runPod = this.getRunPodByName(podName); + if (runPod == null) { + JSONObject mainContainer = buildParams.getJSONObject("mainContainer"); + insertRunPod(cluster, podName, mainContainer, podType, RunPod.RUN_POD_PRIORITY_DEFAULT, timeLimit + 60); + } else { + pod = k8sService.getRunningPod(cluster, podName, createPodTimeLimit); + } + } + if (pod == null) { + boolean canToShenlong = Boolean.FALSE; + boolean canToGpu = Boolean.FALSE; + boolean canToOj = Boolean.FALSE; + if (nodeManager.isCommonPod(podName, buildParams)) { + canToShenlong = nodeManager.canScheduleNormalPodToShenlong(cluster, timeLimit); + canToGpu = nodeManager.canScheduleNormalPodToGpu(cluster, timeLimit); + canToOj = nodeManager.canScheduleNormalPodToOj(cluster, timeLimit); + } + List noList = nodeManager.getNoSchedulableNodes(cluster); + PodCreateStrategy podCreateStrategy = new PodCreateStrategy().setTimeLimit(createPodTimeLimit) + .setNormalPodCanShenlong(canToShenlong).setNormalPodCanGpu(canToGpu).setNormalPodCanOj(canToOj).setNoSchedulableNodes(noList) + .setPodNoSchedulableWeight(sysConfigService.getPodNoSchedulableWeight()) + .setAutoscaleNodeWeight(clusterManager.getScaleNodeWeight(cluster)); + + //读redis锁 + boolean deleteRedisLock = StringUtils.isNotEmpty(JedisUtil.get(RunPodService.DELETE_POD_REDIS_LOCK_KEY + podName)); + //递归读锁,阻塞当前线程线程,等待其他线程释放锁 + while (deleteRedisLock) { + deleteRedisLock = StringUtils.isNotEmpty(JedisUtil.get(RunPodService.DELETE_POD_REDIS_LOCK_KEY + podName)); + } + //没有锁,或已经释放锁,可以创pod + createPodResult = k8sService.createPod(cluster, podName, tpiID, type, buildParams.getString("containers"), + podCreateStrategy, null, bigDataFile, null, buildParams.getString("buildID"), + buildParams.getString("tpmIdentifier"), buildParams.getBooleanValue("mountTestCaseDir")); + pod = createPodResult.getPod(); + if (Boolean.TRUE.equals(createPodResult.getSuccess())) {// pod创建成功时,记录pod所在节点ip + String nodeIp = K8sUtils.getNodeIp(pod); + if (StringUtils.isNotEmpty(nodeIp)) { + updateRunPodNodeIp(podName, K8sUtils.getNodeIp(pod)); + } + } else { + pod = null; + } + nodeManager.processCreatePodResult(cluster, createPodResult); + } + if (pod != null) { + // 按需创建端口映射svc + Integer needPortMapping = buildParams.getInteger("needPortMapping"); + if (needPortMapping != null && needPortMapping != -1 && needPortMapping != 0) { + if (!k8sService.svcExist(cluster, podName)) { + Integer nodePort = buildParams.getInteger("nodePort"); + logger.info("create run pod service, tipID: {}, needPort: {}, nodePort: {}", tpiID, needPortMapping, + nodePort); + k8sService.createService(cluster, podName, tpiID, needPortMapping, nodePort); + updateRunPodSvcPort(podName, nodePort.toString()); + } + } + } + createPodResult.setPod(pod); + } catch (Exception e) { + logger.error("创建pod失败 tpiID: {}", tpiID, e); + createPodResult.setStatus(CreatePodResult.CREATE_POD_STATUS_FAIL_DELETE_NOW); + } + + // 异常情况下记录当前状态 + if (createPodResult.getPod() == null) { + k8sService.describePod(cluster, podName); + } + + return createPodResult; + } + + + @Transactional + public Pod createJupyterPod(final String cluster, String podName, String tpiID, String baseName, String containers, Integer podType, Integer delayDeleteTime, String identifier) { + RunPod runPod = this.getRunPodForUpdate(podName); + if (runPod == null) { + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + this.insertRunPod(cluster, podName, mainContainer, podType, RunPod.RUN_POD_PRIORITY_DEFAULT, delayDeleteTime); + } else { + updateRunPod(runPod, delayDeleteTime); + } + // jupyter创建pod暂没处理超时!!! + PodCreateStrategy podCreateStrategy = new PodCreateStrategy().setNormalPodCanShenlong(false).setNormalPodCanOj(false) + .setAutoscaleNodeWeight(clusterManager.getScaleNodeWeight(cluster)).setTimeLimit(120);// jupyter允许创建耗时120s + Map envs = Collections.singletonMap(JUPYTER_PW_ENV, RandomStringUtils.randomAlphanumeric(16)); + CreatePodResult createPodResult = k8sService.createPod(cluster, podName, tpiID, baseName, containers, podCreateStrategy, envs, identifier, null, null); + Pod pod = createPodResult.getPod(); + if (Boolean.TRUE.equals(createPodResult.getSuccess())) {// pod创建成功时,记录pod所在节点ip + String nodeIp = K8sUtils.getNodeIp(pod); + if (StringUtils.isNotEmpty(nodeIp)) { + updateRunPodNodeIp(podName, K8sUtils.getNodeIp(pod)); + } + } else { + pod = null; + } + return pod; + } + + @Transactional + public Pod createWebsshPod(final String cluster, String podName, String tpiID, String type, String containers, + Integer podType, Integer delayDeleteTime, String bigDataFile, Boolean createImage) { + RunPod runPod = this.getRunPodForUpdate(podName); + if (runPod == null) { + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + this.insertRunPod(cluster, podName, mainContainer, podType, RunPod.RUN_POD_PRIORITY_DEFAULT, delayDeleteTime); + } else { + updateRunPod(runPod, delayDeleteTime); + } + //设置pod创建的最长时间限制等于pod所需所有容器启动时间限制之和 + int createPodTimeLimit = Math.max(ContainerUtil.getContainersStartTime(containers), 15); + // webssh创建pod暂没处理超时!!! + PodCreateStrategy podCreateStrategy = new PodCreateStrategy().setNormalPodCanShenlong(false).setNormalPodCanOj(false) + .setAutoscaleNodeWeight(clusterManager.getScaleNodeWeight(cluster)).setTimeLimit(createPodTimeLimit); + // 随机生成自定义pwd + CreatePodResult createPodResult = k8sService.createPod(cluster, podName, tpiID, type, containers, podCreateStrategy, Collections.singletonMap(WEBSSH_PW_ENV ,RandomStringUtils.randomAlphanumeric(16)), bigDataFile, createImage, null); + Pod pod = createPodResult.getPod(); + if (Boolean.TRUE.equals(createPodResult.getSuccess())) {// pod创建成功时,记录pod所在节点ip + String nodeIp = K8sUtils.getNodeIp(pod); + if (StringUtils.isNotEmpty(nodeIp)) { + updateRunPodNodeIp(podName, K8sUtils.getNodeIp(pod)); + } + } else { + pod = null; + } + return pod; + } + + @Transactional + public Pod createVncPod(final String cluster, String podName, String tpiID, String baseName, String containers, Integer podType, Integer delayDeleteTime, String bigDataFile, Boolean createImage) { + RunPod runPod = this.getRunPodForUpdate(podName); + if (runPod == null) { + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + this.insertRunPod(cluster, podName, mainContainer, podType, RunPod.RUN_POD_PRIORITY_DEFAULT, delayDeleteTime); + } else { + updateRunPod(runPod, delayDeleteTime); + } + //设置pod创建的最长时间限制等于pod所需所有容器启动时间限制之和 + int createPodTimeLimit= ContainerUtil.getContainersStartTime( containers ); + createPodTimeLimit = Math.max(createPodTimeLimit, 15); + // vnc创建pod暂没处理超时!!! + PodCreateStrategy podCreateStrategy = new PodCreateStrategy().setNormalPodCanShenlong(Boolean.FALSE) + .setNormalPodCanOj(Boolean.FALSE) + .setPodNoSchedulableWeight(sysConfigService.getPodNoSchedulableWeight()) + .setAutoscaleNodeWeight(clusterManager.getScaleNodeWeight(cluster)) + .setTimeLimit(createPodTimeLimit); + nodeManager.processShenlongPodStrategy(cluster, podCreateStrategy, containers); + CreatePodResult createPodResult = k8sService.createPod(cluster, podName, tpiID, baseName, containers, + podCreateStrategy, Collections.singletonMap(VNC_PW_ENV, RandomStringUtils.randomAlphanumeric(16)), bigDataFile, createImage, null); + Pod pod = createPodResult.getPod(); + if (Boolean.TRUE.equals(createPodResult.getSuccess())) {// pod创建成功时,记录pod所在节点ip + String nodeIp = K8sUtils.getNodeIp(pod); + if (StringUtils.isNotEmpty(nodeIp)) { + updateRunPodNodeIp(podName, K8sUtils.getNodeIp(pod)); + } + } else { + pod = null; + } + return pod; + } + + public RunPod getRunPodForUpdate(String podName) { + return runPodMapper.selectRunPodForUpdate(podName); + } + + public RunPod getRunPodByName(String podName) { + return runPodMapper.selectByName(podName); + } + + public RunPod getSshRunPodByTpiID(String tpiID) { + return runPodMapper.selectSshRunPodByTpiID(tpiID); + } + + public RunPod insertRunPod(final String cluster, String podName, JSONObject mainContainer, Integer runPodType, Long priority, int timeLimit) { + RunPod runPod = new RunPod(); + runPod.setName(podName); + runPod.setCluster(cluster); + runPod.setRunPodType(runPodType); + runPod.setPriority(priority); + runPod.setCreateBy(appConfig.getBridgeInstanceName()); + runPod.setImageName(mainContainer.getString("imageName")); + runPod.setCpuLimit(Double.parseDouble(mainContainer.getString("cpuLimit"))); + Integer memoryLimit = Integer.parseInt(mainContainer.getString("memoryLimit").replace("M", "")); + runPod.setMemoryLimit(memoryLimit); + runPod.setCpuRequest(Double.parseDouble(mainContainer.getString("cpuRequest"))); + Integer memoryRequest = Integer.parseInt(mainContainer.getString("memoryRequest").replace("M", "")); + runPod.setMemoryRequest(memoryRequest); + LocalDateTime now = LocalDateTime.now(); + + runPod.setCreateTime(now); + runPod.setUpdateTime(now); + LocalDateTime expireTime = now.plusSeconds(timeLimit); + runPod.setExpireTime(expireTime); + + runPodMapper.insertSelective(runPod); + return runPod; + } + + private void updateRunPod(RunPod runPod, int timeLimit) { + + RunPod param = new RunPod(); + param.setId(runPod.getId()); + LocalDateTime now = LocalDateTime.now(); + param.setUpdateTime(now); + now = now.plusSeconds(timeLimit); + param.setExpireTime(now); + + runPodMapper.updateByPrimaryKeySelective(param); + } + + public void updateRunPodSvcPort(String podName, String svcPort) { + RunPodQueryParam param = new RunPodQueryParam(); + param.setName(podName); + param.setSvcPort(svcPort); + runPodMapper.updatePortByName(param); + } + + public void updateRunPodSshPort(String podName, String sshPort) { + RunPodQueryParam param = new RunPodQueryParam(); + param.setName(podName); + param.setSshPort(sshPort); + runPodMapper.updatePortByName(param); + } + + public void updateRunPodNodeIp(String podName, String nodeIp) { + RunPodQueryParam param = new RunPodQueryParam(); + param.setName(podName); + param.setNodeIp(nodeIp); + runPodMapper.updatePortByName(param); + } + + /** + * 统计run_pod + * @return + */ + public List statNodeRes() { + return runPodMapper.statNodeRes(null); + } + + public Integer getServicePort(String podName) { + RunPod runPod = getRunPodByName(podName); + if (runPod != null) { + String port = runPod.getSvcPort(); + if (StringUtils.isNotEmpty(port)) { + return Integer.parseInt(port); + } + } + return null; + } + + public String getRunPodCluster(String tpiID, Integer podType) { + String podName = TpUtils.buildEvaPodName(tpiID, podType); + RunPod runPod = this.getRunPodByName(podName); + if (runPod != null) { + return runPod.getCluster(); + } + return null; + } + + public List podNumStatGroupByNodeIp(String cluster) { + return runPodMapper.podNumStatGroupByNodeIp(cluster); + } + + public List getRunPodsByNodeIp(String cluster, String nodeIp) { + RunPodQueryParam runPodQueryParam = new RunPodQueryParam(); + runPodQueryParam.setCluster(cluster); + runPodQueryParam.setNodeIp(nodeIp); + return runPodMapper.selectRunPods(runPodQueryParam); + } + + public void deleteRunPodByNodeIp(String cluster, String nodeIp) { + RunPodQueryParam runPodQueryParam = new RunPodQueryParam(); + runPodQueryParam.setCluster(cluster); + runPodQueryParam.setExpireTime(LocalDateTime.now()); + runPodQueryParam.setNodeIp(nodeIp); + runPodMapper.updateExpireTimeByNodeIp(runPodQueryParam); + } + + /** + * 杀死运行中的评测进程 + */ + public void killEvaProcess(String tpiID) { + List podList = k8sService.getPodList(TpCsts.LOCAL_CLUSTER, TpCsts.TPI_ID, tpiID); + podList.forEach( pod -> + K8sPodCommandExecutor.execCommandInPod(pod.getMetadata().getName(), new String[]{"bash", "/data/platform/eva/kill.sh"}, 3) + ); + } + + @Transactional + public Pod createVscodePod(String cluster, String podName, String tpiID, String type, String containers, Integer podType, int delayDeleteTime, String bigDataFile, Boolean createImage) { + RunPod runPod = this.getRunPodForUpdate(podName); + if (runPod == null) { + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + this.insertRunPod(cluster, podName, mainContainer, podType, RunPod.RUN_POD_PRIORITY_DEFAULT, delayDeleteTime); + } else { + updateRunPod(runPod, delayDeleteTime); + } + //设置pod创建的最长时间限制等于pod所需所有容器启动时间限制之和 + int createPodTimeLimit = Math.max(ContainerUtil.getContainersStartTime(containers), 15); + + // vscode创建pod暂没处理超时!!! + PodCreateStrategy podCreateStrategy = new PodCreateStrategy().setNormalPodCanShenlong(false).setNormalPodCanOj(false) + .setAutoscaleNodeWeight(clusterManager.getScaleNodeWeight(cluster)).setTimeLimit(createPodTimeLimit); + + CreatePodResult createPodResult = k8sService.createPod(cluster, podName, tpiID, type, containers, podCreateStrategy, null, bigDataFile, createImage, null); + Pod pod = createPodResult.getPod(); + // pod创建成功时,记录pod所在节点ip + if (Boolean.TRUE.equals(createPodResult.getSuccess())) { + String nodeIp = K8sUtils.getNodeIp(pod); + if (StringUtils.isNotEmpty(nodeIp)) { + updateRunPodNodeIp(podName, K8sUtils.getNodeIp(pod)); + } + } else { + pod = null; + } + return pod; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/service/SecurityContextService.java b/common/src/main/java/com/imitate/common/k8s/service/SecurityContextService.java new file mode 100644 index 0000000..1b112b8 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/service/SecurityContextService.java @@ -0,0 +1,42 @@ +package com.imitate.common.k8s.service; + + +import com.imitate.common.k8s.mapper.SecurityContextConfigMapper; +import com.imitate.common.k8s.pojo.SecurityContextConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class SecurityContextService { + + private static final Logger logger = LoggerFactory.getLogger(SecurityContextService.class); + + private volatile Map securityContextConfigMap = new HashMap<>(); + + @Autowired + private SecurityContextConfigMapper securityContextConfigMapper; + + public SecurityContextConfig getSecurityContextConfig(String cluster, String imageName) { + SecurityContextConfig config = securityContextConfigMap.get(imageName); + if (config != null && cluster.equals(config.getCluster())) { + logger.info("getSecurityContextConfig success, config: {}", config); + return config; + } + return null; + } + + public void refreshSecurityContextConfigs() { + List configs = securityContextConfigMapper.selectAll(); + Map securityContextConfigMapTemp = new HashMap<>(); + for (SecurityContextConfig config : configs) { + securityContextConfigMapTemp.put(config.getImageName(), config); + } + securityContextConfigMap = securityContextConfigMapTemp; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/util/ContainerUtil.java b/common/src/main/java/com/imitate/common/k8s/util/ContainerUtil.java new file mode 100644 index 0000000..fdb134f --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/util/ContainerUtil.java @@ -0,0 +1,227 @@ +package com.imitate.common.k8s.util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.k8s.bean.BridgeContainer; +import io.fabric8.kubernetes.api.model.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public final class ContainerUtil { + private static final Logger log = LoggerFactory.getLogger(ContainerUtil.class); + public static JSONObject getMainContainer(String containers) { + JSONArray jsonArray = JSONArray.parseArray(containers); + JSONObject mainContainer = jsonArray.getJSONObject(0); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject container = jsonArray.getJSONObject(i); + if ("main".equals(container.getString("type"))) { + mainContainer = container; + } + } + String image = mainContainer.getString("image"); + String[] imageInfo = image.split(":"); + String imageName = imageInfo[0]; + String imageVersion = imageInfo.length > 1 ? imageInfo[1] : ""; + mainContainer.put("imageName", imageName); + mainContainer.put("imageVersion", imageVersion); + return mainContainer; + } + + + public static BridgeContainer parseBridgeContainer(String container) { + BridgeContainer bc = new BridgeContainer(); + + JSONObject obj = JSONObject.parseObject(container); + bc.setName(obj.getString("imageName")); + bc.setImage(obj.getString("image")); + bc.setCpuLimit(Double.parseDouble(obj.getString("cpuLimit"))); + bc.setCpuRequest(Double.parseDouble(obj.getString("cpuRequest"))); + Double memoryLimit = Double.parseDouble(obj.getString("memoryLimit").replace("M", "")); + bc.setMemoryLimit(memoryLimit); + Double memoryRequest = Double.parseDouble(obj.getString("memoryRequest").replace("M", "")); + bc.setMemoryRequest(memoryRequest); + + return bc; + } + + public static double getRequestCpu(String containers) { + + double sum = 0; + List cs = parseContainers(containers); + for (Container c:cs) { + String cpu = c.getResources().getRequests().get("cpu").getAmount(); + try{ + if( cpu!=null ) + sum += Double.parseDouble(cpu); + }catch ( Exception e ) + { + log.error( "web层传递的containers参数中的cpuRequest不合法,cpuRequest:{}",cpu,e ); + } + + } + + return sum; + + } + + public static double getLimitCpu(String containers) { + + double sum = 0; + List cs = parseContainers(containers); + for (Container c:cs) { + String cpu = c.getResources().getLimits().get("cpu").getAmount(); + try{ + if( cpu!=null ) + sum += Double.parseDouble(cpu); + }catch ( Exception e ) + { + log.error( "web层传递的containers参数中的cpuLimit不合法,cpuLimit:{}",cpu,e ); + } + } + + return sum; + + } + + public static double getRequestMemory(String containers) { + + double sum = 0L; + List cs = parseContainers(containers); + for (Container c:cs) { + String memory = c.getResources().getRequests().get("memory").getAmount(); + memory=memory.replaceAll( "M","" ); + try{ + sum += Double.parseDouble(memory); + }catch ( Exception e ) + { + log.error( "web层传递的containers参数中的memoryRequest不合法,memoryRequest:{}",memory,e ); + } + } + + return sum; + + } + + public static double getLimitMemory(String containers) { + + double sum = 0; + List cs = parseContainers(containers); + for (Container c:cs) { + String memory = c.getResources().getLimits().get("memory").getAmount(); + memory=memory.replaceAll( "M","" ); + try{ + sum += Double.parseDouble(memory); + }catch ( Exception e ) + { + log.error( "web层传递的containers参数中的memoryLimit不合法,memoryLimit:{}",memory,e ); + } + } + + return sum; + + } + + public static List parseContainers(String containers) { + + JSONArray jsonArray = JSONArray.parseArray(containers); + List containerList = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + Container container = new Container(); + JSONObject props = jsonArray.getJSONObject(i); + container.setName(props.getString("image").split(":")[0]); + container.setImage(props.getString("image")); + + container.setImagePullPolicy("IfNotPresent"); + + Map limits = new HashMap<>(2); + limits.put("cpu", new Quantity(props.getString("cpuLimit"))); + limits.put("memory", new Quantity(props.getString("memoryLimit"))); + + // 最低资源限制 + Map requests = new HashMap<>(2); + requests.put("cpu", new Quantity(props.getString("cpuRequest"))); + requests.put("memory", new Quantity(props.getString("memoryRequest"))); + + ResourceRequirements resourceRequirements = new ResourceRequirements(limits, requests); + container.setResources(resourceRequirements); + // 次类别设置启动脚本为不包含ssh启动命令的脚本 + String cmdParams = props.getString("cmd_params"); + if ("sub".equals(props.getString("type"))) { + container.setCommand(StringUtils.isNotEmpty(cmdParams) ? Arrays.asList(cmdParams.split(",")) : Collections.singletonList("/start2.sh")); + containerList.add(container); + } else { + if(StringUtils.isNotEmpty(cmdParams)){ + container.setCommand(Arrays.asList(cmdParams.split(","))); + } + containerList.add(0, container); + } + } + + return containerList; + + } + + public static void setLifecycleStartCommand(Container container, List command) { + Lifecycle lifecycle = new Lifecycle(); + Handler postStart = new Handler(); + ExecAction exec = new ExecAction(); + exec.setCommand(command); + postStart.setExec(exec); + lifecycle.setPostStart(postStart); + container.setLifecycle(lifecycle); + } + + public static boolean imagesMatch(String containers, List containersList) { + List images = getContainersImages(containers); + boolean match = false; + for (String image : images) { + for (Container container : containersList) { + match = false; + if (image.equals(container.getImage())) { + match = true; + break; + } + } + if (!match) { + return false; + } + } + return true; + } + + private static List getContainersImages(String containers) { + JSONArray jsonArray = JSONArray.parseArray(containers); + List images = new ArrayList<>(jsonArray.size()); + for (int i = 0; i < jsonArray.size(); i++) { + images.add(jsonArray.getJSONObject(i).getString("image")); + } + return images; + } + + /** + * 获取实训所需容器启动时间之和 + * @param containers 实训所需容器 + * @return 实训所需容器启动时间之和 + */ + public static int getContainersStartTime(String containers) { + JSONArray jsonArray = JSONArray.parseArray(containers); + int containersStartTime = 0; + for (int i = 0; i < jsonArray.size(); i++) { + try { + String startTime = jsonArray.getJSONObject(i).getString("startTime"); + if (startTime != null) { + containersStartTime += Integer.parseInt(startTime); + } else { + containersStartTime += 15; + } + } catch (Exception e) { + log.error("web层传递的startTime参数不合法,containers参数:{}", containers, e); + } + } + return containersStartTime; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/util/GetPodErrorReasonUtil.java b/common/src/main/java/com/imitate/common/k8s/util/GetPodErrorReasonUtil.java new file mode 100644 index 0000000..f9d6b63 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/util/GetPodErrorReasonUtil.java @@ -0,0 +1,30 @@ +package com.imitate.common.k8s.util; + + +import com.imitate.common.bean.ShellResult; +import com.imitate.common.util.ShellUtil; + +/** + * @author huqifeng + * @Time 2021-7-23 + * @Description 当k8s pod出现问题时,执行shell语句返回错误的原因 + */ +public final class GetPodErrorReasonUtil { + + /** + * @Description 当k8s pod为Failed状态时,执行shell语句返回错误的原因 + * @param podName + * @return errReasonOut,即pod为Failed状态的原因 + */ + public static String getPodFailedReason( String podName ) + { + String cmd = "timeout 2 kubectl " + "describe pod " + podName + " | awk '$2==\"Failed\"'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + String errReasonOut=null; + if (result.getExitStatus() == 0) + { + errReasonOut = result.getOut(); + } + return errReasonOut; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/util/K8sClientUtil.java b/common/src/main/java/com/imitate/common/k8s/util/K8sClientUtil.java new file mode 100644 index 0000000..7ec2005 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/util/K8sClientUtil.java @@ -0,0 +1,93 @@ +package com.imitate.common.k8s.util; + + +import com.imitate.common.sys.pojo.ClusterConfig; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.bean.BeanFactory; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class K8sClientUtil { + + private static volatile Map clients = new HashMap<>(); + + public static KubernetesClient getClient(String cluster) { + KubernetesClient client = clients.get(cluster); + if (client == null) { + synchronized (K8sClientUtil.class) { + client = clients.get(cluster); + if (client == null) { + ClusterConfigService clusterConfigService = (ClusterConfigService) BeanFactory + .getObejct("clusterConfigService"); + AppConfig appConfig = (AppConfig) BeanFactory.getObejct("appConfig"); + + ClusterConfig clusterConfig = clusterConfigService.getClusterConfig(cluster); + if (StringUtils.isNotEmpty(clusterConfig.getUsername())) { + Config config = new ConfigBuilder().withMasterUrl(clusterConfig.getMasterUrl()) + .withNamespace("default").withTrustCerts(true).withUsername(clusterConfig.getUsername()) + .withPassword(clusterConfig.getPassword()) + .withMaxConcurrentRequests(appConfig.getK8sMaxConcurrentRequests()) + .withMaxConcurrentRequestsPerHost(appConfig.getK8sMaxConcurrentRequestsPerHost()) + .withWebsocketTimeout(150000) + .withWebsocketPingInterval(0) + .build(); + client = new DefaultKubernetesClient(config); + } else if (StringUtils.isNotEmpty(clusterConfig.getCaCertData())) { + Config config = new ConfigBuilder().withMasterUrl(clusterConfig.getMasterUrl()) + .withNamespace("default").withTrustCerts(true) + .withCaCertData(clusterConfig.getCaCertData()) + .withClientCertData(clusterConfig.getClientCerData()) + .withClientKeyData(clusterConfig.getClientKeyData()) + .withMaxConcurrentRequests(appConfig.getK8sMaxConcurrentRequests()) + .withMaxConcurrentRequestsPerHost(appConfig.getK8sMaxConcurrentRequestsPerHost()) + .withWebsocketTimeout(150000) + .withWebsocketPingInterval(0) + .build(); + client = new DefaultKubernetesClient(config); + } + Map tmp = new HashMap<>(clients); + tmp.put(cluster, client); + clients = tmp; + } + } + } + return client; + } + + public static void refreshK8sClients() { + ClusterConfigService clusterConfigService = (ClusterConfigService) BeanFactory + .getObejct("clusterConfigService"); + List availableClusters = clusterConfigService.getAvailableClusters(); + + List list = new ArrayList<>(clients.keySet()); + for (String cluster : list) { + boolean exist = false; + for (String availableCluster : availableClusters) { + if (cluster.equals(availableCluster)) { + exist = true; + break; + } + } + + if (!exist) { + removeClient(cluster); + } + } + + } + + private static synchronized void removeClient(String cluster) { + Map tmp = new HashMap<>(clients); + tmp.remove(cluster); + clients = tmp; + } +} diff --git a/common/src/main/java/com/imitate/common/k8s/util/K8sPodCommandExecutor.java b/common/src/main/java/com/imitate/common/k8s/util/K8sPodCommandExecutor.java new file mode 100644 index 0000000..8da17be --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/util/K8sPodCommandExecutor.java @@ -0,0 +1,119 @@ +package com.imitate.common.k8s.util; + +/** + * k8s 命令执行器 + * + * @author 威少 + */ + +import com.imitate.common.bean.ShellResult; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.ExecListener; +import io.fabric8.kubernetes.client.dsl.ExecWatch; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.imitate.common.constant.TpCsts.*; + + +@Slf4j +public class K8sPodCommandExecutor { + + /** + * Pod中执行命令 + * + * @param podName pod名 + * @param cmd 命令 + * @param timeout 超时时间 + * @return 执行结果 + */ + public static ShellResult execCommandInPod(String podName, String[] cmd, long timeout) { + return execCommandInPod(LOCAL_CLUSTER, DEFAULT_NAMESPACE, podName, cmd, timeout); + } + + /** + * Pod中执行命令 + * + * @param cluster 集群 + * @param namespace 命名空间 + * @param podName pod名 + * @param cmd 命令 + * @param timeout 超时时间 + * @return 执行结果 + */ + public static ShellResult execCommandInPod(String cluster, String namespace, String podName, String[] cmd, long timeout) { + ShellResult result = new ShellResult(ShellResult.ExitStatus.FAIL.getCode(), StringUtils.EMPTY); + KubernetesClient client = K8sClientUtil.getClient(cluster); + Pod pod = client.pods().inNamespace(namespace).withName(podName).get(); + CompletableFuture data = new CompletableFuture<>(); + try (ExecWatch ignored = execCmd(client, pod, data, cmd)) { + result = data.get(timeout, TimeUnit.SECONDS); + result.setExitStatus(ShellResult.ExitStatus.SUCCESS.getCode()); + } catch (TimeoutException e) { + log.warn("exec command in pod timeout, cmd: {}, pod: {}", cmd, podName, e); + result.setExitStatus(ShellResult.ExitStatus.TIMEOUT.getCode()); + } catch (Exception e) { + log.error("exec command in pod failure, cmd: {}, pod: {}", cmd, podName, e); + } + + log.info("execute command: {} in pod: {} with timeout: {}, result: {}", cmd, podName, timeout, result); + + return result; + } + + /** + * 执行命令 + * + * @param client 客户端 + * @param pod pod + * @param data 返回数据future + * @param command 命令 + */ + private static ExecWatch execCmd(KubernetesClient client, Pod pod, CompletableFuture data, String... command) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errorChannelStream = new ByteArrayOutputStream(); + String firstContainerName = pod.getSpec().getContainers().get(0).getName(); + return client.pods() + .inNamespace(pod.getMetadata().getNamespace()) + .withName(pod.getMetadata().getName()) + .inContainer(firstContainerName) + .writingOutput(outputStream) + .writingError(outputStream) + .writingErrorChannel(errorChannelStream) + .usingListener(new PodBashExecListener(data, outputStream)) + .exec(command); + } + + static class PodBashExecListener implements ExecListener { + + private CompletableFuture data; + private ByteArrayOutputStream baos; + + public PodBashExecListener(CompletableFuture data, ByteArrayOutputStream baos) { + this.data = data; + this.baos = baos; + } + + @SneakyThrows + @Override + public void onFailure(Throwable t, Response failureResponse) { + if (failureResponse == null) { + data.completeExceptionally(t); + } else { + data.complete(new ShellResult(failureResponse.code(), failureResponse.body())); + } + } + + @Override + public void onClose(int code, String reason) { + data.complete(new ShellResult(code, baos.toString())); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/k8s/util/K8sUtils.java b/common/src/main/java/com/imitate/common/k8s/util/K8sUtils.java new file mode 100644 index 0000000..4838a73 --- /dev/null +++ b/common/src/main/java/com/imitate/common/k8s/util/K8sUtils.java @@ -0,0 +1,371 @@ +package com.imitate.common.k8s.util; + + +import com.google.common.collect.Lists; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.pojo.ErrorPodInfo; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.util.ShellUtil; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.client.utils.PodStatusUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDateTime; +import java.util.List; + +public final class K8sUtils { + + private static final Logger logger = LoggerFactory.getLogger(K8sUtils.class); + + public static int getNodePort(Service svc) { + List list = svc.getSpec().getPorts(); + for (ServicePort sp : list) { + return sp.getNodePort(); + } + throw new RuntimeException("svc: " + svc.getMetadata().getName() + " 中没有找到NodePort"); + } + + public static double getNodeAllocatableCpu(Node node) { + String amount = node.getStatus().getAllocatable().get("cpu").getAmount(); + return parseCpuAmount(amount); + } + + public static double getNodeCapacityCpu(Node node) { + String amount = node.getStatus().getCapacity().get("cpu").getAmount(); + return parseCpuAmount(amount); + } + + public static double getRequestCpu(Pod pod) { + String amount = pod.getSpec().getContainers().get(0).getResources().getRequests().get("cpu").getAmount(); + return parseCpuAmount(amount); + } + + public static double getNodeAllocatableMemory(Node node) { + String amount = node.getStatus().getAllocatable().get("memory").getAmount(); + return parseMemoryAmount(amount); + } + + public static double getNodeCapacityMemory(Node node) { + String amount = node.getStatus().getCapacity().get("memory").getAmount(); + return parseCpuAmount(amount); + } + + public static boolean isOjNode(Node node) { + String label = node.getMetadata().getLabels().get(TpCsts.OJ_LABEL_KEY); + return StringUtils.isNotEmpty(label) && TpCsts.OJ_LABEL_VALUE.equals(label); + } + + public static boolean isOjPod(Pod pod) { + String label = pod.getMetadata().getLabels().get(TpCsts.OJ_LABEL_KEY); + return StringUtils.isNotEmpty(label) && TpCsts.OJ_LABEL_VALUE.equals(label); + } + + private static double parseCpuAmount(String amount) { + // cpu可能 的形式 100m, "2", 1 + amount = amount.trim().replaceAll("\"", ""); // \"有这种情况? + if (amount.endsWith("m")) { + amount = amount.substring(0, amount.length() - 1); + return Double.parseDouble(amount) / 1000; + } else { + return Double.parseDouble(amount); + } + } + + private static double parseMemoryAmount(String amount) { + // memory返回的格式为:16267956Ki.需要去除最后两个字符 + return Double.parseDouble(amount.substring(0, amount.length() - 2)) / 1024; + } + + public static String getIp(Node node) { + return node.getStatus().getAddresses().get(0).getAddress(); + } + + public static String getLabel(Node node, String labelKey) { + return node.getMetadata().getLabels().get(labelKey); + } + + public static String getNodeIp (Pod pod) { + return pod.getStatus().getHostIP(); + } + + public static String getNodeName (Node node) { + return node.getMetadata().getName(); + } + + public static String getNodeName(Pod pod) { + return pod == null ? null : pod.getSpec().getNodeName(); + } + + public static String getPodName(Pod pod) { + return pod == null ? null : pod.getMetadata().getName(); + } + + public static boolean isErrImagePull(Pod pod) { + List list = pod.getStatus().getContainerStatuses(); + for (ContainerStatus cs : list) { + ContainerState state = cs.getState(); + if (state == null) { + continue; + } + ContainerStateWaiting wating = state.getWaiting(); + if (wating == null) { + continue; + } + boolean r = "ErrImagePull".equals(wating.getReason()); + if (r) { + return true; + } + } + return false; + } + + /** + * @author huqifeng + * @Time 2021-7-19 + * @Description 查询错误pod在节点上是否在服务器上不存在对应的镜像 + * @param pod k8s pod + * @return 不存在的镜像列表 + */ + public static List podImagesNotExist(Pod pod) { + List containerStatusList = pod.getStatus().getContainerStatuses(); + List podConditionList = pod.getStatus().getConditions(); + String nodeIp = pod.getStatus().getHostIP(); + //带版本号的镜像名 + String imageNameWithVersion; + //镜像名imageName + String imageName; + //缺失的镜像 + List imageList = Lists.newArrayList(); + //判断pod创建失败是否是由于镜像拉取失败而引起的 + for (PodCondition podCondition : podConditionList) { + if ("False".equals(podCondition.getStatus()) && "ContainersNotReady".equals(podCondition.getReason())) { + String podConditionMessage = podCondition.getMessage(); + if (podConditionMessage.startsWith("containers with unready status: [")) { + //获取镜像名 + for (ContainerStatus containerStatus : containerStatusList) { + imageName=containerStatus.getName(); + imageNameWithVersion = containerStatus.getImage(); + if (imageNameWithVersion == null) { + continue; + } + //执行shell命令查询镜像是否存在 + //镜像不存在,则保存缺失的镜像名 + if (!K8sUtils.searchDockerImage(imageName, nodeIp)) { + imageList.add(imageNameWithVersion); + } + } + } + } + } + return imageList; + } + + /** + * @author huqifeng + * @Time 2021-7-19 + * @Description 判断k8s节点上是否有某一个镜像 + * @param imageName 镜像名 + * @param nodeIp k8s节点ip + * @return 若存在该镜像,返回true,否则返回false + */ + public static boolean searchDockerImage(String imageName, String nodeIp) { + String remoteConnection = String.format(SysConfigCsts.REMOTE_CONN_PREFIX_DEF, nodeIp); + //在shell中执行查询镜像语句 + String getCommand = remoteConnection + " docker images | grep " + imageName + " | awk '{print $1}'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(getCommand); + + return 0 == result.getExitStatus() && !"".equals(result.getOut()); + } + + + + public static boolean isReadyStatus(Node node) { + List conditions = node.getStatus().getConditions(); + // 只统计Ready状态的节点 + for (NodeCondition condition : conditions) { + if ("Ready".equalsIgnoreCase(condition.getType()) + && "True".equalsIgnoreCase(condition.getStatus())) { + return true; + } + } + return false; + } + + /** + * 容器是否处于running的状态 + */ + public static boolean isPodRunning(Pod pod) { + return PodStatusUtil.isRunning(pod) && + pod.getStatus().getContainerStatuses().stream().allMatch( + status -> status.getReady() != null + && status.getState() != null + && status.getState().getRunning() != null); + } + + public static boolean isNewCapacityNode(Node node, Integer time) { + String createTimestamp = node.getMetadata().getCreationTimestamp(); + if (createTimestamp.endsWith("Z")) { + createTimestamp = createTimestamp.substring(0, createTimestamp.length() - 1); + } + // 转化为东八区时间 + LocalDateTime createTime = LocalDateTime.parse(createTimestamp).plusHours(8); + return LocalDateTime.now().plusMinutes(-time).isBefore(createTime); + } + + /** + * @author huqifeng + * @Time 2021-8-6 + * @Description 当pod创建超时时,捕获pod的phase,以及处于该phase的原因 + * @param podStatus pod状况 + * @param podName pod名称 + * @param nodeIp 节点ip + * @param errorPodInfo pod失败信息对象 + */ + public static void catchPodPhaseReasonMessage(ErrorPodInfo errorPodInfo, PodStatus podStatus, String podName, String nodeIp ) + { + String podPhase=podStatus.getPhase(); + String podStatusReason=podStatus.getReason(); + String podStatusMessage=podStatus.getMessage(); + errorPodInfo.setPodPhase(podPhase); + errorPodInfo.setPodStatusReason(podStatusReason); + errorPodInfo.setPodConditionMessage(podStatusMessage); + logger.info( "创建pod {}失败,nodeIp: {},podPhase: {},podStatusReason: {},podStatusMessage: {}",podName,nodeIp,podPhase,podStatusReason,podStatusMessage ); + } + + /** + * @author huqifeng + * @Time 2021-8-6 + * @Description 当pod创建超时时,捕获podCondition的状态,以及处于该状态的原因 + * @param podStatus pod状况 + * @param podName pod名称 + * @param nodeIp 节点ip + * @param errorPodInfo pod失败信息对象 + */ + public static void catchPodConditionReasonMessage(ErrorPodInfo errorPodInfo,PodStatus podStatus,String podName,String nodeIp) + { + List conditions=podStatus.getConditions(); + String podConditionType = null; + String podConditionStatus = null; + String podConditionReason = null; + String podConditionMessage = null; + for(PodCondition podCondition:conditions) + { + podConditionType = StringUtils.join(podConditionType,podCondition.getType(),"_"); + podConditionStatus = StringUtils.join(podConditionStatus,podCondition.getType(),":",podCondition.getStatus(),"_"); + podConditionReason = StringUtils.join(podConditionReason,podCondition.getType(),":",podCondition.getReason(),"_"); + podConditionMessage = StringUtils.join(podConditionMessage,podCondition.getType(),":",podCondition.getMessage(),"_"); + logger.info("创建pod {}失败,nodeIp: {},podConditionType: {},podConditionReason: {},podConditionMessage: {}",podName,nodeIp,podCondition.getType(),podCondition.getReason(),podCondition.getMessage()); + } + errorPodInfo.setPodConditionType(podConditionType); + errorPodInfo.setPodConditionStatus(podConditionStatus); + errorPodInfo.setPodConditionReason(podConditionReason); + errorPodInfo.setPodConditionMessage(podConditionMessage); + } + + /** + * @author huqifeng + * @Time 2021-8-6 + * @Description 当pod创建超时时,捕获Container的状态,以及处于该状态的原因 + * @param podStatus pod状况 + * @param podName pod名称 + * @param nodeIp 节点ip + * @param errorPodInfo pod失败信息对象 + */ + public static void catchContainerStateReasonMessage(ErrorPodInfo errorPodInfo, PodStatus podStatus, String podName, String nodeIp ) + { + List containerStatuses=podStatus.getContainerStatuses(); + String image; + String containerStatusReady = null; + String containerStatusWaiting = null; + String containerStatusWaitingReason = null; + String containerStatusWaitingMessage = null; + String containerStatusTerminated = null; + String containerStatusTerminatedReason = null; + String containerStatusTerminatedMessage = null; + for( ContainerStatus containerStatus:containerStatuses ) + { + image = containerStatus.getImage(); + if (containerStatus.getReady()) { + containerStatusReady=StringUtils.join(containerStatusReady,image,"_","true","_"); + } + else { + containerStatusReady=StringUtils.join(containerStatusReady,image,"_","false","_"); + } + ContainerState containerState=containerStatus.getState(); + ContainerStateWaiting waiting = containerState.getWaiting(); + //containerStatus为waiting状态 + if (waiting!=null) { + containerStatusWaiting = StringUtils.join(containerStatusWaiting,image,"_","true","_"); + containerStatusWaitingReason = StringUtils.join(containerStatusWaitingReason,image,"_",waiting.getReason(),"_"); + containerStatusWaitingMessage = StringUtils.join(containerStatusWaitingMessage,image,"_",waiting.getMessage(),"_"); + logger.info( "创建pod {}失败,nodeIp: {},image: {},ContainerState: waiting,ContainerStateReason: {},ContainerStateMessage: {}",podName,nodeIp,image,waiting.getReason(),waiting.getMessage() ); + } + else { + containerStatusWaiting = StringUtils.join(containerStatusWaiting,image,"_","null","_"); + containerStatusWaitingReason = StringUtils.join(containerStatusWaitingReason,image,"_","null","_"); + containerStatusWaitingMessage = StringUtils.join(containerStatusWaitingMessage,image,"_","null","_"); + } + ContainerStateTerminated terminated=containerState.getTerminated(); + //containerStatus为terminated状态 + if (terminated!=null) { + containerStatusTerminated = StringUtils.join(containerStatusTerminated,image,"_","true","_"); + containerStatusTerminatedReason = StringUtils.join(containerStatusTerminatedReason,image,"_",terminated.getReason(),"_"); + containerStatusTerminatedMessage = StringUtils.join(containerStatusTerminatedMessage,image,"_",terminated.getMessage(),"_"); + logger.info( "创建pod {}失败,nodeIp: {},image: {},ContainerState: terminated,ContainerStateReason: {},ContainerStateMessage: {}",podName,nodeIp,image,terminated.getReason(),terminated.getMessage() ); + } + else { + containerStatusTerminated = StringUtils.join(containerStatusTerminated,image,"_","null","_"); + containerStatusTerminatedReason = StringUtils.join(containerStatusTerminatedReason,image,"_","null","_"); + containerStatusTerminatedMessage = StringUtils.join(containerStatusTerminatedMessage,image,"_","null","_"); + } + } + errorPodInfo.setContainerStatusReady(containerStatusReady); + errorPodInfo.setContainerStatusWaiting(containerStatusWaiting); + errorPodInfo.setContainerStatusWaitingReason(containerStatusWaitingReason); + errorPodInfo.setContainerStatusWaitingMessage(containerStatusWaitingMessage); + errorPodInfo.setContainerStatusTerminated(containerStatusTerminated); + errorPodInfo.setContainerStatusTerminatedReason(containerStatusTerminatedReason); + errorPodInfo.setContainerStatusTerminatedMessage(containerStatusTerminatedMessage); + } + + /** + * @author huqifeng + * @Time 2021-8-9 + * @Description 当pod创建超时时,捕获Container的状态,以及处于该状态的原因 + * @param podStatus pod状况 + * @param podName pod名称 + * @param nodeIp 节点ip + */ + public static void createErrorPodInfo(ErrorPodInfo errorPodInfo, PodStatus podStatus, String podName, String nodeIp, String tpiID, String buildID, boolean imageExist) + { + errorPodInfo.setErrorTime(LocalDateTime.now()); + errorPodInfo.setPodName(podName); + errorPodInfo.setNodeIp(nodeIp); + errorPodInfo.setTpiID(tpiID); + errorPodInfo.setBuildID(buildID); + if (imageExist) { + errorPodInfo.setImageExist("true"); + } + else { + errorPodInfo.setImageExist("false"); + } + K8sUtils.catchPodPhaseReasonMessage(errorPodInfo, podStatus, podName, nodeIp); + K8sUtils.catchPodConditionReasonMessage(errorPodInfo, podStatus, podName, nodeIp); + K8sUtils.catchContainerStateReasonMessage(errorPodInfo, podStatus, podName, nodeIp); + } + + /** + * 获取环境变量 + */ + public static String getEnv(Pod pod, String key){ + String value = ""; + if (!CollectionUtils.isEmpty(pod.getSpec().getContainers().get(0).getEnv())) { + value = pod.getSpec().getContainers().get(0).getEnv().stream().filter(envVar -> envVar.getName().equals(key)).findFirst().get().getValue(); + } + return value; + } +} diff --git a/common/src/main/java/com/imitate/common/shiro/config/FilterConfig.java b/common/src/main/java/com/imitate/common/shiro/config/FilterConfig.java new file mode 100644 index 0000000..61a04b9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/config/FilterConfig.java @@ -0,0 +1,22 @@ +package com.imitate.common.shiro.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.web.filter.DelegatingFilterProxy; + +/** + * Filter配置 + * @author yanchao + */ +public class FilterConfig { + @Bean + public FilterRegistrationBean shiroFilterRegistration(){ + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(new DelegatingFilterProxy("shiroFilter")); + registrationBean.addInitParameter("targetFilteerLifecycle","true"); + registrationBean.setEnabled(true); + registrationBean.setOrder(Integer.MAX_VALUE - 1); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } +} diff --git a/common/src/main/java/com/imitate/common/shiro/config/OAuth2Filter.java b/common/src/main/java/com/imitate/common/shiro/config/OAuth2Filter.java new file mode 100644 index 0000000..8d03b2d --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/config/OAuth2Filter.java @@ -0,0 +1,108 @@ +package com.imitate.common.shiro.config; + +import com.google.gson.Gson; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.shiro.realm.OAuth2Token; +import com.imitate.common.util.R; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * oauth2过滤器 + * @author yanchao + */ +public class OAuth2Filter extends AuthenticatingFilter { + + /** + * 取token + * @author yanchao + */ + @Override + protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { + //获取请求token + String token = getRequestToken((HttpServletRequest) servletRequest); + if(StringUtils.isBlank(token)){ + return null; + } + return new OAuth2Token(token); + } + + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){ + return true; + } + return false; + } + + + /** + * 认证之前调用的方法 + * @author yanchao + * @return boolean + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + //获取请求中的token,如果不存在,直接返回401 + String token = getRequestToken((HttpServletRequest)request); + if(StringUtils.isBlank(token)){ + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader("Access-Control-Allow-Credentials","true"); + httpResponse.setHeader("Access-Control-Allow-Origin", "*"); + httpResponse.setContentType("application/json;charset=UTF-8"); + R r = R.error(ErrorCodeEnum.NO_AUTH.getValue(),ErrorCodeEnum.NO_AUTH.getDescription()); + String json = new Gson().toJson(r); + httpResponse.getWriter().print(json); + return false; + } + return executeLogin(request,response); + } + + + /** + * 登陆认证失败调用的方法 + * @author yanchao + * @return boolean + */ + @Override + protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setContentType("application/json;charset=utf-8"); + httpResponse.setHeader("Access-Control-Allow-Credentials","true"); + httpResponse.setHeader("Access-Control-Allow-Origin", "*"); + try { + //处理登录失败的异常 + R r = R.error(ErrorCodeEnum.NO_AUTH.getValue(),ErrorCodeEnum.NO_AUTH.getDescription()); + String json = new Gson().toJson(r); + httpResponse.getWriter().print(json); + }catch (IOException e1){ + } + return false; + } + + + /** + * 获取请求的token + * @author yanchao + */ + private String getRequestToken(HttpServletRequest httpRequest){ + //从header中获取token + String token = httpRequest.getHeader("token"); + //如果header中不存在token,则从参数中获取 + if(StringUtils.isBlank(token)){ + token = httpRequest.getParameter("token"); + } + return token; + } + +} diff --git a/common/src/main/java/com/imitate/common/shiro/config/ShiroConfig.java b/common/src/main/java/com/imitate/common/shiro/config/ShiroConfig.java new file mode 100644 index 0000000..8c0a2aa --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/config/ShiroConfig.java @@ -0,0 +1,106 @@ +package com.imitate.common.shiro.config; + + +import com.imitate.common.shiro.realm.OAuth2Realm; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Shiro配置类 + * @author yanchao + */ +@Configuration +public class ShiroConfig { + + /** + * 会话管理器 + * @author yanchao + */ + @Bean("sessionManager") + public SessionManager sessionManager(){ + DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); + sessionManager.setSessionValidationSchedulerEnabled(true); + sessionManager.setSessionIdCookieEnabled(true); + return sessionManager; + } + + /** + * 安全管理器 + * @author yanchao + */ + @Bean("securityManager") + public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager){ + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(oAuth2Realm); + securityManager.setSessionManager(sessionManager); + return securityManager; + } + + /** + * shiro过滤器工厂 + * @author yanchao + */ + @Bean("shiroFilter") + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ + ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); + shiroFilter.setSecurityManager(securityManager); + //oauth + Map filters = new HashMap<>(); + filters.put("oauth2",new OAuth2Filter()); + shiroFilter.setFilters(filters); + + Map filterMap = new LinkedHashMap<>(); + //filterMap.put("/**","oauth2"); + filterMap.put("/**","anon"); + shiroFilter.setFilterChainDefinitionMap(filterMap); + return shiroFilter; + } + + + + + /** + * 启用Shiro注解,保证实现了shiro内部lifecycle函数的bean执行 + * @author yanchao + */ + @Bean("lifecycleBeanPostProcessor") + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ + return new LifecycleBeanPostProcessor(); + } + + + + /** + * 启用shiro注解 + * @author yanchao + */ + @Bean + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ + DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); + proxyCreator.setProxyTargetClass(true); + return proxyCreator; + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } + + + + +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysMenuMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysMenuMapper.java new file mode 100644 index 0000000..b0beccf --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysMenuMapper.java @@ -0,0 +1,16 @@ +package com.imitate.common.shiro.mapper; + + +import com.imitate.common.shiro.pojo.SysMenu; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +/** + * 权限菜单 + * @author yanchao + */ +@Repository +@Mapper +public interface SysMenuMapper extends BaseMapper { +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMapper.java new file mode 100644 index 0000000..8d804f5 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMapper.java @@ -0,0 +1,31 @@ +package com.imitate.common.shiro.mapper; + + + +import com.imitate.common.shiro.pojo.SysRole; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; +import java.util.List; + +/** + * 角色管理 + * @author yanchao + */ +@Mapper +@Repository +public interface SysRoleMapper extends BaseMapper { + + /** + * 查询用户创建的角色ID列表 + */ + List queryRoleIdList(Long createUserId); + + + /** + * 获取角色详情 + */ + SysRole get(@Param(value = "id") Long id); + +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMenuMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..31b6ff0 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysRoleMenuMapper.java @@ -0,0 +1,29 @@ +package com.imitate.common.shiro.mapper; + + +import com.imitate.common.shiro.pojo.SysRoleMenu; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 角色与菜单对应关系 + * @author yanchao + */ +@Mapper +@Repository +public interface SysRoleMenuMapper extends BaseMapper { + + /** + * 根据角色ID,获取菜单ID列表 + */ + List queryMenuIdList(Long roleId); + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); + +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysTokenMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysTokenMapper.java new file mode 100644 index 0000000..ad1464b --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysTokenMapper.java @@ -0,0 +1,14 @@ +package com.imitate.common.shiro.mapper; + + +import com.imitate.common.shiro.pojo.SysToken; +import com.imitate.common.util.BaseMapper; +import org.springframework.stereotype.Repository; + +/** + * 登录会话 + * @author yanchao + */ +@Repository +public interface SysTokenMapper extends BaseMapper { +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysUserMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysUserMapper.java new file mode 100644 index 0000000..468d245 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysUserMapper.java @@ -0,0 +1,23 @@ +package com.imitate.common.shiro.mapper; + + + +import com.imitate.common.shiro.pojo.SysUser; +import com.imitate.common.util.BaseMapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 后台用户mapper + * @author yanchao + */ +@Repository +public interface SysUserMapper extends BaseMapper { + + /** + * 查询用户的所有权限 + */ + List queryAllPerms(Long id); + +} diff --git a/common/src/main/java/com/imitate/common/shiro/mapper/SysUserRoleMapper.java b/common/src/main/java/com/imitate/common/shiro/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..92465db --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/mapper/SysUserRoleMapper.java @@ -0,0 +1,37 @@ +package com.imitate.common.shiro.mapper; + + + +import com.imitate.common.shiro.pojo.SysUserRole; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 用户与角色对应关系 + * @author yanchao + */ +@Mapper +@Repository +public interface SysUserRoleMapper extends BaseMapper { + + /** + * 根据用户ID,获取角色ID列表 + */ + List queryRoleIdList(Long userId); + + + /** + * 根据角色ID数组,批量删除 + */ + int deleteBatch(Long[] roleIds); + + + List selectInUserId(@Param("ids") List ids); + + + +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysMenu.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysMenu.java new file mode 100644 index 0000000..0ec73ff --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysMenu.java @@ -0,0 +1,57 @@ +package com.imitate.common.shiro.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import javax.persistence.Transient; + + +/** + * 系统权限表+菜单表 二合一表 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_menu") +public class SysMenu extends AbstractDO { + + private Long parentId; + + + @Transient + private String parentName; + + /** + * 菜单名称 + */ + private String name; + + /** + * 菜单URL + */ + private String url; + + /** + * 授权码(多个用逗号分隔,如:user:list,user:create + */ + private String perms; + + /** + * 类型 0:目录 1:菜单 2:按钮 + */ + private Integer type; + + /** + * 菜单图标 + */ + private String icon; + + /** + * 排序 + */ + private Integer orderNum; + +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysRole.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysRole.java new file mode 100644 index 0000000..f716685 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysRole.java @@ -0,0 +1,46 @@ +package com.imitate.common.shiro.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import javax.persistence.Transient; +import java.util.List; + + +/** + * 系统角色表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_role") +public class SysRole extends AbstractDO { + /** + * 角色名称 + */ + private String roleName; + + /** + * 备注 + */ + private String remark; + + + /** + * 创建者ID + */ + private Long createUserId; + + + @Transient + private List menuIdList; + + + /** + * 角色下的用户数量 + */ + @Transient + private Integer userNum; +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysRoleMenu.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysRoleMenu.java new file mode 100644 index 0000000..06f5d27 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysRoleMenu.java @@ -0,0 +1,29 @@ +package com.imitate.common.shiro.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; + + +/** + * 系统角色权限表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_role_menu") +public class SysRoleMenu extends AbstractDO { + + /** + * 角色ID + */ + private Long roleId; + + /** + * 菜单ID + */ + private Long menuId; + +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysToken.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysToken.java new file mode 100644 index 0000000..7650865 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysToken.java @@ -0,0 +1,23 @@ +package com.imitate.common.shiro.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.util.Date; + +/** + * 登录会话表 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_login_token") +public class SysToken extends AbstractDO { + private Long userId; + private String action; + private String value; + //过期时间 + private Date expireTime; +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysUser.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysUser.java new file mode 100644 index 0000000..cca5de5 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysUser.java @@ -0,0 +1,38 @@ +package com.imitate.common.shiro.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.util.Date; + +/** + * 后台用户表 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_user") +public class SysUser extends AbstractDO { + private String username; + private String password; + private String mobile; + private Byte status; + private String userHeadimg; + private String wxExtra; + private String wxOpenid; + private Byte admin; + private String nickName; + + private Byte sex; + private String province; + private String city; + private Date subscribeTime; + private String lastLoginIp; + private Date lastLoginOn; + + + +} diff --git a/common/src/main/java/com/imitate/common/shiro/pojo/SysUserRole.java b/common/src/main/java/com/imitate/common/shiro/pojo/SysUserRole.java new file mode 100644 index 0000000..1e8cd8e --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/pojo/SysUserRole.java @@ -0,0 +1,29 @@ +package com.imitate.common.shiro.pojo; + + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.persistence.Table; + + +/** + * 系统用户角色表 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_user_role") +public class SysUserRole extends AbstractDO { + + /** + * 用户ID + */ + private Long userId; + + /** + * 角色ID + */ + private Long roleId; + +} diff --git a/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Realm.java b/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Realm.java new file mode 100644 index 0000000..275fa1c --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Realm.java @@ -0,0 +1,81 @@ +package com.imitate.common.shiro.realm; + + +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.shiro.pojo.SysToken; +import com.imitate.common.shiro.pojo.SysUser; +import com.imitate.common.shiro.service.ShiroService; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Set; + +/** + * shiro realm + * @author yanchao + */ +@Component +public class OAuth2Realm extends AuthorizingRealm { + + @Override + public boolean supports(AuthenticationToken token){ + return token instanceof OAuth2Token; + } + + @Autowired + private ShiroService shiroService; + + + /** + * 授权 + * @author yanchao + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + SysUser user = (SysUser) principal.getPrimaryPrincipal(); + List roleIds = shiroService.queryRoleIdList(user.getId()); + if(roleIds != null){ + //用户权限查询 + Set permsSet = shiroService.getUserPermissions(user); + info.setStringPermissions(permsSet); + } + return info; + } + + /** + * 认证 token + * @author yanchao + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + String accessToken = (String) token.getPrincipal(); + + //根据accessToken 查询用户信息 + SysToken tokenEntity = shiroService.queryByToken(accessToken); + + //token失效 + if(tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()){ + throw new BusinessException(ErrorCodeEnum.LOGIN_EXPIRE_TIME.getValue(),ErrorCodeEnum.LOGIN_EXPIRE_TIME.getDescription()); + } + + //查询用户信息 + SysUser user = shiroService.queryUser(tokenEntity.getUserId()); + + //账号锁定 + if(user.getStatus() == 0){ + throw new BusinessException(ErrorCodeEnum.USER_LOGIN_DISABLE.getValue(),ErrorCodeEnum.USER_LOGIN_DISABLE.getDescription()); + } + return new SimpleAuthenticationInfo(user,accessToken,getName()); + } + +} diff --git a/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Token.java b/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Token.java new file mode 100644 index 0000000..7f07cd0 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/realm/OAuth2Token.java @@ -0,0 +1,26 @@ +package com.imitate.common.shiro.realm; + +import org.apache.shiro.authc.AuthenticationToken; + +/** + * 类似UsernamePasswordToken + * @author yanchao + */ +public class OAuth2Token implements AuthenticationToken{ + private String token; + + public OAuth2Token(String token){ + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } + +} diff --git a/common/src/main/java/com/imitate/common/shiro/service/ShiroService.java b/common/src/main/java/com/imitate/common/shiro/service/ShiroService.java new file mode 100644 index 0000000..d9ecc41 --- /dev/null +++ b/common/src/main/java/com/imitate/common/shiro/service/ShiroService.java @@ -0,0 +1,107 @@ +package com.imitate.common.shiro.service; + + +import com.imitate.common.shiro.mapper.*; +import com.imitate.common.shiro.pojo.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import tk.mybatis.mapper.entity.Example; +import java.util.*; +import java.util.stream.Collectors; + + +/** + * shiro 认证与授权 + * @author yanchao + */ +@Service +public class ShiroService { + + @Autowired + private SysMenuMapper sysMenuMapper; + @Autowired + private SysUserMapper sysUserMapper; + @Autowired + private SysTokenMapper sysTokenMapper; + @Autowired + private SysUserRoleMapper sysUserRoleMapper; + @Autowired + private SysRoleMapper sysRoleMapper; + + + public Set getUserPermissions(SysUser eduSysUser) { + List permsList; + + //系统管理员 ,拥有最高权限 + if(eduSysUser.getAdmin() == 1){ + List menuList = sysMenuMapper.selectAll(); + permsList = new ArrayList<>(menuList.size()); + for (SysMenu menu:menuList){ + permsList.add(menu.getPerms()); + } + }else{ + permsList = sysUserMapper.queryAllPerms(eduSysUser.getId()); + } + //用户权限列表 + Set permsSet = new HashSet<>(); + for (String perms:permsList){ + permsSet.addAll(Arrays.asList(perms.trim())); + } + return permsSet; + } + + + + public SysToken queryByToken(String token) { + Example example = new Example(SysToken.class); + example.createCriteria().andEqualTo("value",token); + return sysTokenMapper.selectOneByExample(example); + } + + public SysUser queryUser(Long userId) { + return sysUserMapper.selectByPrimaryKey(userId); + } + + + public void saveOrUpdate(Long userId, List roleIdList) { + //先删除用户与角色关系 + Example example = new Example(SysUserRole.class); + example.createCriteria().andEqualTo("userId",userId); + sysUserRoleMapper.deleteByExample(example); + + if(roleIdList == null || roleIdList.size() == 0){ + return ; + } + + //保存用户与角色关系 + List list = new ArrayList<>(roleIdList.size()); + for(Long roleId : roleIdList){ + SysUserRole sysUserRoleEntity = new SysUserRole(); + sysUserRoleEntity.setUserId(userId); + sysUserRoleEntity.setRoleId(roleId); + list.add(sysUserRoleEntity); + } + sysUserRoleMapper.insertList(list); + } + + public List queryRoleIdList(Long userId) { + List roleIds = null; + Example example = new Example(SysUserRole.class); + example.createCriteria().andEqualTo("userId",userId); + List eduSysUserRoles = sysUserRoleMapper.selectByExample(example); + if(eduSysUserRoles.size() > 0){ + roleIds = eduSysUserRoles.stream().map(SysUserRole::getRoleId).collect(Collectors.toList()); + } + return roleIds; + } + + public List queryRoleList(Long userId) { + List roleIds = queryRoleIdList(userId); + Example example = new Example(SysRole.class); + example.createCriteria().andIn("id",roleIds); + return sysRoleMapper.selectByExample(example); + } + + + +} diff --git a/common/src/main/java/com/imitate/common/sys/competitor/LockCompetitor.java b/common/src/main/java/com/imitate/common/sys/competitor/LockCompetitor.java new file mode 100644 index 0000000..27f14a0 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/competitor/LockCompetitor.java @@ -0,0 +1,19 @@ +package com.imitate.common.sys.competitor; + +/** + * 锁竞争者。 + */ +public interface LockCompetitor { + + /** + * 竞争者是否已经赢得锁 + * + * @return + */ + boolean winLock(); + + /** + * 失去锁 + */ + void loseLock(); +} diff --git a/common/src/main/java/com/imitate/common/sys/competitor/PodScheduleLockCompetitor.java b/common/src/main/java/com/imitate/common/sys/competitor/PodScheduleLockCompetitor.java new file mode 100644 index 0000000..a340126 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/competitor/PodScheduleLockCompetitor.java @@ -0,0 +1,122 @@ +package com.imitate.common.sys.competitor; + + +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.pojo.SysConfig; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import org.apache.commons.lang3.ThreadUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.time.Duration; + +/** + * pod调度竞争。 + */ +@Component +public class PodScheduleLockCompetitor implements LockCompetitor { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static volatile boolean OPEN_COMPETE = true; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private AppConfig appConfig; + + /** + * 是否调度节点 + */ + private volatile boolean isScheduler = false; + /** + * 是否调度失败 + */ + private volatile boolean isScheduleFail = false; + + @PostConstruct + public void init() { + new Thread(() -> { + try { + competeScheduler(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }).start(); + } + + /** + * 竞争成为调度节点 + */ + private void competeScheduler() throws InterruptedException { + final SysConfig configParam = new SysConfig(); + configParam.setName(SysConfigCsts.CONFIG_POD_SCHEDULE); + configParam.setVal(appConfig.getBridgeInstanceName()); + while (true) { + SysConfig sysConfig = null; + try { + if (OPEN_COMPETE) { + sysConfig = sysConfigService.lockSysConfig(configParam); + if (sysConfig != null) { + logger.info("本节点获得pod_schedule锁, bridge节点:{}", appConfig.getBridgeInstanceName()); + isScheduler = true; + // 重置调度状态 + isScheduleFail = false; + ThreadUtils.sleep(Duration.ofDays(500)); + while (isScheduler) { + sysConfig = sysConfigService.refreshLockExpireTime(configParam); + if (isScheduleFail) { + isScheduler = false; + break; + } + isScheduler = (sysConfig != null); + if (!isScheduler) { + break; + } + + ThreadUtils.sleep(Duration.ofDays(500)); + } + } + } + } catch (Throwable t) { + logger.error("竞争pod_schedule锁异常", t); + } finally { + isScheduler = false; + if (sysConfig != null) { + try { + sysConfigService.releasSysConfigLock(configParam); + } catch (Throwable t) { + logger.error("释放竞争pod_schedule锁异常", t); + } + } + } + + ThreadUtils.sleep(Duration.ofDays(1000)); + + } + } + + @Override + public boolean winLock() { + return isScheduler; + } + + @Override + public void loseLock() { + logger.info("本节点失去pod_schedule锁, bridge节点:{}", appConfig.getBridgeInstanceName()); + isScheduleFail = true; + + } + + public void closeCompete() { + OPEN_COMPETE = false; + isScheduleFail = true; + logger.info("本节点失去pod_schedule锁并停止参与竞争, bridge节点:{}", appConfig.getBridgeInstanceName()); + } + +} diff --git a/common/src/main/java/com/imitate/common/sys/constant/SysConfigCsts.java b/common/src/main/java/com/imitate/common/sys/constant/SysConfigCsts.java new file mode 100644 index 0000000..14e3d7c --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/constant/SysConfigCsts.java @@ -0,0 +1,457 @@ +package com.imitate.common.sys.constant; + +import java.util.Arrays; +import java.util.List; + +public interface SysConfigCsts { + + /** + * 已锁定。 + */ + String STATUS_ENABLE = "1"; + /** + * 未锁定。 + */ + String STATUS_DISABLE = "0"; + + /** + * k8s redis key: 可用端口 + */ + String KEY_USABLE_PORT_K8S = "usable_port_k8s"; + + /** + * windows redis key: 可用端口 + */ + String KEY_USABLE_PORT_WINDOWS = "usable_port_windows"; + + /** + * virtual redis key: 虚拟机可用端口 + */ + String KEY_USABLE_PORT_VIRTUAL = "usable_port_virtual"; + + String K8S_USABLE_PORT_RANGE = "k8s_usable_port_range"; + /** + * 最小可用端口 + */ + int K8S_MIN_USABLE_PORT_DEF = 40000; + /** + * 最大可用端口 + */ + int K8S_MAX_USABLE_PORT_DEF = 60000; + + String WEBSSH_PW_ENV = "WEBSSH_PW"; + String VNC_PW_ENV = "VNC_PW"; + String JUPYTER_PW_ENV = "JUPYTER_PW"; + String DEFAULT_LINUX_USERNAME = "root"; + + /** + * 配置项:pod管理 + */ + String CONFIG_POD_MANAGE = "pod_manage"; + + /** + * 配置项:windows管理 + */ + String CONFIG_WINDOWS_MANAGE = "windows_manage"; + + /** + * 配置项:pod调度 + */ + String CONFIG_POD_SCHEDULE = "pod_schedule"; + + /** + * 配置项:pod管理 + */ + String CONFIG_OJ_POD_MANAGE = "oj_pod_manage"; + + /** + * 配置项:pod管理 + */ + String CONFIG_OJ_NODE_ELASTIC_SCALING_MANAGE = "oj_node_elastic_scaling_manage"; + + /** + * 配置项:可用端口重置 + */ + String CONFIG_PORT_RESET = "port_reset"; + + /** + * 集群闸门:开 + */ + String CLUSTER_SWITCH_ON = "on"; + + /** + * 所有神龙节点request cpu阈值百分比 + */ + String SHENLONG_NODE_OVERLOAD_PERCENT = "shenlong_overload_percent"; + + int SHENLONG_NODE_OVERLOAD_PERCENT_DEF = 50; + + /** + * 所有Gpu节点request cpu阈值百分比 + */ + String GPU_NODE_OVERLOAD_PERCENT = "gpu_overload_percent"; + + int GPU_NODE_OVERLOAD_PERCENT_DEF = 20; + + /** + * 通用镜像,80%的pod都是用的这些镜像 + */ + String COMMON_IMAGE_NAME = "common_image_name"; + + List COMMON_IMAGE_NAME_DEF = Arrays.asList("gcc-ssh","openjdk-ssh","python3-ssh"); + + /** + * 检测节点是否正常所用的镜像 + */ + String CHECK_POD_CONTAINER_IMAGE = "gcc-ssh:v1.0"; + + /** + * 创建评测pod最大花费时间 + */ + String CREATE_POD_COST_MAX_TIME = "create_pod_cost_max_time"; + + long CREATE_POD_COST_MAX_TIME_DEF = 9000; // 9秒钟 + + /** + * k8s节点压力负载过期时间 + */ + String NODE_OVERLOAD_EXPIRE_TIME = "node_overload_expire_time"; + + long NODE_OVERLOAD_EXPIRE_TIME_DEF = 10000; // 10秒钟 + + /** + * 神龙节点压力负载过期时间 + */ + String SHENLONG_NODE_OVERLOAD_EXPIRE_TIME = "shenlong_node_overload_expire_time"; + + long SHENLONG_NODE_OVERLOAD_EXPIRE_TIME_DEF = 1800000; // 30分钟 + + /** + * GPU节点压力负载过期时间 + */ + String GPU_NODE_OVERLOAD_EXPIRE_TIME = "gpu_node_overload_expire_time"; + + long GPU_NODE_OVERLOAD_EXPIRE_TIME_DEF = 30 * 60 * 1000; // 30分钟 + + /** + * 不可调度节点权重 + */ + String POD_NO_SCHEDULABLE_WEIGHT = "pod_no_schedulable_weight"; + + int POD_NO_SCHEDULABLE_WEIGHT_DEF = 100; + + /** + * 自动扩容节点权重 + */ + String AUTOSCALE_NODE_WEIGHT = "k8s.io/cluster-autoscaler"; + + int AUTOSCALE_NODE_WEIGHT_DEF = 1; + + /** + * vnc pod初始化存活时间(秒) + */ + String VNC_POD_SURVIVAL_SECOND = "vnc_pod_survival_second"; + + int VNC_POD_SURVIVAL_SECOND_DEF = 1800; // 30分钟 + + /** + * vnc pod延长存活时间(秒) + */ + String VNC_POD_DELAY_SECOND = "vnc_pod_delay_second"; + + int VNC_POD_DELAY_SECOND_DEF = 1800; // 30分钟 + + /** + * oj节点最大算力百分比 + */ + String OJ_NODE_MAX_POW_PERCENT = "oj_node_max_pow_percent"; + + int OJ_NODE_MAX_POW_PERCENT_DEF = 80; + + /** + * oj节点压力负载过期时间 + */ + String OJ_NODE_OVERLOAD_EXPIRE_TIME = "oj_node_overload_expire_time"; + + long OJ_NODE_OVERLOAD_EXPIRE_TIME_DEF = 60000; // 30分钟 + + /** + * oj节点最大评测超时数 + */ + String OJ_NODE_MAX_EVA_TIMEOUT_NUM = "oj_node_max_eva_timeout_num"; + + int OJ_NODE_MAX_EVA_TIMEOUT_NUM_DEF = 10; + + /** + * oj节点开启复用 + */ + String OJ_NODE_OPEN_REUSE = "oj_node_open_reuse"; + + boolean OJ_NODE_OPEN_REUSE_DEF = Boolean.TRUE; + + /** + * GPU节点开启复用 + */ + String GPU_NODE_OPEN_REUSE = "gpu_node_open_reuse"; + + boolean GPU_NODE_OPEN_REUSE_DEF = Boolean.TRUE; + + /** + * 节点最大cpu使用率,超过则认定节点压力过大 + */ + String NODE_CPU_MAX_USAGE_RATE = "node_cpu_max_usage_rate"; + + int NODE_CPU_MAX_USAGE_RATE_DEF = 90; + + + /** + * 节点最小cpu使用率,小于则认定节点空闲 + */ + String NODE_CPU_MIN_USAGE_RATE = "node_cpu_min_usage_rate"; + + int NODE_CPU_MIN_USAGE_RATE_DEF = 50; + + + /** + * 无状态扩容集群 request cpu 比例上限 + */ + String STATELESS_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT = "stateless_scale_cluster_request_cpu_ratio_limit"; + + double STATELESS_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF = 50; + + /** + * 无状态扩容集群 request memory 比例上限 + */ + String STATELESS_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT = "stateless_scale_cluster_request_memory_ratio_limit"; + + double STATELESS_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF = 45; + + /** + * 无状态不能扩容集群 request cpu 比例上限 + */ + String STATELESS_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT = "stateless_not_scale_cluster_request_cpu_ratio_limit"; + + double STATELESS_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF = 70; + + /** + * 无状态不能扩容集群 request memory 比例上限 + */ + String STATELESS_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT = "stateless_not_scale_cluster_request_memory_ratio_limit"; + + double STATELESS_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF = 65; + + /** + * 有状态扩容集群 request cpu 比例上限 + */ + String STATEFUL_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT = "stateful_scale_cluster_request_cpu_ratio_limit"; + + double STATEFUL_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF = 50; + + /** + * 有状态扩容集群 request memory 比例上限 + */ + String STATEFUL_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT = "stateful_scale_cluster_request_memory_ratio_limit"; + + double STATEFUL_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF = 45; + + /** + * 有状态不能扩容集群 request cpu 比例上限 + */ + String STATEFUL_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT = "stateful_not_scale_cluster_request_cpu_ratio_limit"; + + double STATEFUL_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF = 70; + + /** + * 有状态不能扩容集群 request memory 比例上限 + */ + String STATEFUL_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT = "stateful_not_scale_cluster_request_memory_ratio_limit"; + + double STATEFUL_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF = 65; + + String REMOTE_CLUSTER_GIT_HOST = "remoteClusterGitHost"; + + String HIDE_NFS_INFO_COMMAND = "hide_nfs_info_command"; + + /** + * bridge中心节点关闭评测功能开关 + */ + String BRIDGE_CENTER_CLOSE_EVA = "bridge_center_close_eva"; + + boolean BRIDGE_CENTER_CLOSE_EVA_DEF = Boolean.FALSE; + + /** + * redis删除pod记录过期时间 + */ + String DELETE_POD_REDIS_EXPIRE_TIME = "delete_pod_redis_expire_time"; + + long DELETE_POD_REDIS_EXPIRE_TIME_DEF = 3000; // 3秒 + + /** + * 扩容节点权重1:节点充分参与分配,等同固定节点 + */ + String SCALE_NODE_WEIGHT_1 = "scale_node_weight_1"; + /** + * 扩容节点权重2:节点参与分配,但尽量减少分配。压力较小,将回收。 + */ + String SCALE_NODE_WEIGHT_2 = "scale_node_weight_2"; + /** + * 扩容节点权重3:节点不参与新分配。将回收。 + */ + String SCALE_NODE_WEIGHT_3 = "scale_node_weight_3"; + + /** + * oj节点开启复用 + */ + String OJ_NODE_OPEN_ELASTIC_SCALING = "oj_node_open_elastic_scaling"; + + boolean OJ_NODE_OPEN_ELASTIC_SCALING_DEF = Boolean.FALSE; + + /** + * 云主机开关 + */ + String CLOUD_HOST_SWITCH = "cloud_host_switch"; + boolean CLOUD_HOST_SWITCH_DEF = Boolean.TRUE; + + /** + * 清除oj扩容节点的普通评测pod等待时间 + */ + String CLEAR_OJ_CAPACITY_NODE_POD_DELAY_TIME = "clear_oj_capacity_node_pod_delay_time"; + + int CLEAR_OJ_CAPACITY_NODE_POD_DELAY_TIME_DEF = 10000; + + String OJ_OVERLOAD_RATIO_CAPACITY = "oj_overload_ratio_capacity"; + + double OJ_OVERLOAD_RATIO_CAPACITY_DEF = 1; + + String OJ_OVERLOAD_RATIO_REDUCE = "oj_overload_ratio_reduce"; + + double OJ_OVERLOAD_RATIO_REDUCE_DEF = 0.5; + + String OJ_NODE_BASE_NUM = "oj_node_base_num"; + + int OJ_NODE_BASE_NUM_DEF = 2; + + String CONTAINER_IMAGE_TYPE = "container_image_type"; + + List CONTAINER_IMAGE_TYPE_DEF = Arrays.asList("gpu-big_GPU-big", "gpu-hnjdzy_GPU-hnjdzy", "nvidia_GPU", "shenlong_shenlong"); + + String NODE_EVA_ERROR_MIN_RATIO = "node_eva_error_min_ratio"; + + double NODE_EVA_ERROR_MIN_RATIO_DEF = 1; + + String NODE_EVA_ERROR_CHECK_TIME = "node_eva_error_check_time"; + + int NODE_EVA_ERROR_CHECK_TIME_DEF = 0; + + String OJ_POD_NUM_TO_CPU_PERCENTAGE = "oj_pod_num_to_cpu_percentage"; + + int OJ_POD_NUM_TO_CPU_PERCENTAGE_DEF = 50; + + String REMOTE_CONN_PREFIX = "remote_conn_prefix"; + + String REMOTE_CONN_PREFIX_DEF = "sshpass -pDDpd:lk3+407 ssh -p30122 root@%s"; + + String K8S_IMAGE_PULL_SECRET = "k8s_image_pull_secret"; + + String K8S_IMAGE_PULL_SECRET_DEF = "regcred"; + + String K8S_IMAGE_PULL_URL = "k8s_image_pull_url"; + + String K8S_IMAGE_PULL_URL_DEF = "registry-vpc.cn-hangzhou.aliyuncs.com/educoder/educoder:"; + + String OPEN_NODE_STATUS_CHECK = "open_node_status_check"; + + boolean OPEN_NODE_STATUS_CHECK_DEF = false; + + String NEW_CAPACITY_NODE_WEIGHT = "new_capacity_node_weight"; + + int NEW_CAPACITY_NODE_WEIGHT_DEF = 0; + + String NEW_CAPACITY_NODE_PREPARE_TIME = "new_capacity_node_prepare_time"; + + int NEW_CAPACITY_NODE_PREPARE_TIME_DEF = 0; + + String CREATE_POD_FLOW_CONTROL = "create_pod_flow_control"; + + String CREATE_POD_FLOW_CONTROL_DEF = ""; + + String NEW_CAPACITY_NODE_TIME = "new_capacity_node_time"; + + int NEW_CAPACITY_NODE_TIME_DEF = 5; + + String NODE_MIN_POD_NUM = "node_min_pod_num"; + + int NODE_MIN_POD_NUM_DEF = 15; + + String PRE_REDUCE_NODE_WEIGHT = "pre_reduce_node_weight"; + + int PRE_REDUCE_NODE_WEIGHT_DEF = 0; + + String SCALE_NODE_WEIGHT = "scale_node_weight"; + + int SCALE_NODE_WEIGHT_DEF = 0; + + String LINUX_SHOW_SERVER = "linux_show_server"; + + String LINUX_SHOW_SERVER_DEF = "https://%snat1.vnc.educoder.net/vnc_lite.html?password=headless"; + + String CREATE_POD_ERROR_RETRY_COUNT = "create_pod_error_retry_count"; + + int CREATE_POD_ERROR_RETRY_COUNT_DEF = 0; + + String NODE_EVA_ERROR_MIN_NUM = "node_eva_error_min_num"; + + int NODE_EVA_ERROR_MIN_NUM_DEF = 100; + + String CLOUD_HOST_NAME_CONFIG = "cloud_host_name_config"; + + String NOT_SCALE_NODE_TIME_HOUR = "not_scale_node_time_hour"; + + String JUPYTER_LAB_PUSH_ADD_CMD = "jupyter_lab_push_add_cmd"; + + String JUPYTER_LAB_PUSH_ADD_CMD_DEF = "git status | grep \"modified:\" | awk -F[:] '{print $2}' | xargs git add"; + + String FORWARD_ENTRY_CHECK_CODE = "forward_entry_check_code"; + + int FORWARD_ENTRY_CHECK_CODE_DEF = 0; + + /** + * vscode pod延长存活时间(秒) + */ + String VSCODE_POD_DELAY_SECOND = "vscode_pod_delay_second"; + int VSCODE_POD_DELAY_SECOND_DEF = 1800; + + /** + * HPA min replica + */ + String HPA_MIN_REPLICA = "hpa_min_replica"; + int HPA_MIN_REPLICA_DEF = 10; + + /** + * HPA max replica + */ + String HPA_MAX_REPLICA = "hpa_max_replica"; + int HPA_MAX_REPLICA_DEF = 30; + + /** + * HPA desireCpuPercent + */ + String HPA_DESIRE_CPU_PERCENT = "hpa_desire_cpu_percent"; + int HPA_DESIRE_CPU_PERCENT_DEF = 600; + + /** + * HPA desireMemoryPercent + */ + String HPA_DESIRE_MEMORY_PERCENT = "hpa_desire_memory_percent"; + int HPA_DESIRE_MEMORY_PERCENT_DEF = 1000; + + /** + * long_live_image + */ + String LONG_LIVE_IMAGE = "long_live_image"; + + /** + * long_live_pod + */ + String LONG_LIVE_POD = "long_live_pod"; +} diff --git a/common/src/main/java/com/imitate/common/sys/mapper/ClusterConfigMapper.java b/common/src/main/java/com/imitate/common/sys/mapper/ClusterConfigMapper.java new file mode 100644 index 0000000..8a8b414 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/mapper/ClusterConfigMapper.java @@ -0,0 +1,30 @@ +package com.imitate.common.sys.mapper; + + +import com.imitate.common.sys.pojo.ClusterConfig; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@Mapper +public interface ClusterConfigMapper extends BaseMapper { + + @Override + int insert(ClusterConfig record); + + @Override + int insertSelective(ClusterConfig record); + + + @Override + int updateByPrimaryKeySelective(ClusterConfig record); + + @Override + int updateByPrimaryKey(ClusterConfig record); + + @Override + List selectAll(); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/sys/mapper/SysConfigMapper.java b/common/src/main/java/com/imitate/common/sys/mapper/SysConfigMapper.java new file mode 100644 index 0000000..8daa23a --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/mapper/SysConfigMapper.java @@ -0,0 +1,32 @@ +package com.imitate.common.sys.mapper; + + +import com.imitate.common.sys.pojo.SysConfig; +import com.imitate.common.util.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Mapper +@Repository +public interface SysConfigMapper extends BaseMapper { + + @Override + int insert(SysConfig record); + + @Override + int insertSelective(SysConfig record); + + @Override + int updateByPrimaryKeySelective(SysConfig record); + + @Override + int updateByPrimaryKey(SysConfig record); + + SysConfig selectSysConfigForUpdate(String name); + + SysConfig selectSysConfigByName(String name); + + List selectAllSysConfig(); +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/sys/pojo/ClusterConfig.java b/common/src/main/java/com/imitate/common/sys/pojo/ClusterConfig.java new file mode 100644 index 0000000..3aa9d4b --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/pojo/ClusterConfig.java @@ -0,0 +1,108 @@ +package com.imitate.common.sys.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; + +import javax.persistence.Table; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "cluster_config") +public class ClusterConfig extends AbstractDO { + + /** + * 集群名称 + */ + private String name; + + /** + * 是否本地集群 + */ + private Boolean local; + + /** + * 主开关,on开启,off关闭 + */ + private String mainSwitch; + + /** + * 集群权重 + */ + private Double weight; + + /** + * 自动扩容 + */ + private Boolean autoScale; + + private String masterUrl; + + private String caCertData; + + private String clientCerData; + + private String clientKeyData; + + private String username; + + private String password; + + private String kubeconfig; + + private String localWebSocket; + + private String localSshNodes; + + private String localShowServer; + + private String localVncShowServer; + + private String localGitUrl; + + private String remoteGitUrl; + + private String remoteWebSocket; + + private String remoteSshNodes; + + private String remoteShowServer; + + private String remoteVncShowServer; + + private String blackImages; + + private String whiteImages; + + + + public boolean notSupportImage(String mainImageName) { + if (StringUtils.isEmpty(blackImages)) { + return Boolean.FALSE; + } + String[] blackImagesArray = blackImages.split(","); + for (String blackImage : blackImagesArray) { + if (mainImageName.equals(blackImage)) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + public boolean supportImage(String mainImageName) { + // 无白名单则全部通过 + if (StringUtils.isEmpty(whiteImages)) { + return Boolean.TRUE; + } + String[] whiteImagesArray = whiteImages.split(","); + for (String whiteImage : whiteImagesArray) { + if (mainImageName.equals(whiteImage)) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/sys/pojo/SysConfig.java b/common/src/main/java/com/imitate/common/sys/pojo/SysConfig.java new file mode 100644 index 0000000..23d9c45 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/pojo/SysConfig.java @@ -0,0 +1,29 @@ +package com.imitate.common.sys.pojo; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Table; +import java.time.LocalDateTime; + + +@Data +@EqualsAndHashCode(callSuper = false) +@Table(name = "sys_config") +public class SysConfig extends AbstractDO { + + private String name; + + private String val; + + private Integer expireIn; + + private LocalDateTime expireTime; + + + private String status; + + public static final String SYS_CONFIG_STATUS_DEF = "0"; + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/sys/service/ClusterConfigService.java b/common/src/main/java/com/imitate/common/sys/service/ClusterConfigService.java new file mode 100644 index 0000000..d8978df --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/service/ClusterConfigService.java @@ -0,0 +1,141 @@ +package com.imitate.common.sys.service; + + +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.mapper.ClusterConfigMapper; +import com.imitate.common.sys.pojo.ClusterConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.*; + +/** + * k8s集群配置服务 + */ +@Service +public class ClusterConfigService { + + @Autowired + private ClusterConfigMapper clusterConfigMapper; + + private volatile Map clusterConfigs = new HashMap<>(); + + public Map refreshClusterConfigs() { + Map tmpMap = new HashMap<>(); + List list = clusterConfigMapper.selectAll(); + for (ClusterConfig config : list) { + tmpMap.put(config.getName(), config); + } + clusterConfigs = tmpMap; + return clusterConfigs; + } + + public List getAvailableClusterConfigs() { + List list = new ArrayList<>(); + Collection collections = clusterConfigs.values(); + for (ClusterConfig config : collections) { + if(SysConfigCsts.CLUSTER_SWITCH_ON.equals(config.getMainSwitch())) { + list.add(config); + } + } + return list; + } + + public List getAvailableClusters() { + List list = new ArrayList<>(); + Collection collections = clusterConfigs.values(); + for (ClusterConfig config : collections) { + if(SysConfigCsts.CLUSTER_SWITCH_ON.equals(config.getMainSwitch())) { + list.add(config.getName()); + } + } + return list; + } + + public List getLocalAvailableClusterConfigs() { + List list = new ArrayList<>(); + Collection collections = clusterConfigs.values(); + for (ClusterConfig config : collections) { + if(SysConfigCsts.CLUSTER_SWITCH_ON.equals(config.getMainSwitch()) && config.getLocal()) { + list.add(config); + } + } + return list; + } + + public ClusterConfig getClusterConfig(String cluster) { + ClusterConfig config = clusterConfigs.get(cluster); + if (config == null) { + throw new RuntimeException("unknown cluster " + cluster); + } + return config; + } + + public String getKubeConfig(String cluster) { + ClusterConfig config = getClusterConfig(cluster); + return config.getKubeconfig(); + } + + public String getShowServer(String cluster, boolean isLocal) { + ClusterConfig config = getClusterConfig(cluster); + if (config != null) { + return isLocal ? config.getLocalShowServer() : config.getRemoteShowServer(); + } + return null; + } + + public String getLocalClusterShowServer() { + Collection collections = clusterConfigs.values(); + for (ClusterConfig config : collections) { + if(SysConfigCsts.CLUSTER_SWITCH_ON.equals(config.getMainSwitch()) && config.getLocal()) { + return config.getLocalShowServer(); + } + } + return null; + } + + public String getLocalWebSocket() { + Collection collections = clusterConfigs.values(); + for (ClusterConfig config : collections) { + if(SysConfigCsts.CLUSTER_SWITCH_ON.equals(config.getMainSwitch()) && config.getLocal()) { + return config.getLocalWebSocket(); + } + } + return null; + } + + public String getGitUrl(String cluster, Boolean isLocal) { + ClusterConfig config = getClusterConfig(cluster); + if (config != null) { + return isLocal ? config.getLocalGitUrl() : config.getRemoteGitUrl(); + } + return null; + } + + + public String getVncShowServer(String cluster, boolean isLocal, int port, String password) { + ClusterConfig config = getClusterConfig(cluster); + if (config != null) { + String showServer = isLocal ? config.getLocalVncShowServer() : config.getRemoteVncShowServer(); + showServer = String.format(showServer, port, StringUtils.isEmpty(password) ? "headless" : password); + return showServer; + } + return null; + } + + public String getLocalSshNode(String cluster, String hostIp) { + ClusterConfig config = getClusterConfig(cluster); + + String sshNodes = config.getLocalSshNodes(); + if ("nodes".equals(sshNodes)) { + return hostIp; + } + String[] arr = sshNodes.split(","); + // TODO 暂时随机,还是改为轮询的好 + int random = (int) (System.currentTimeMillis() % arr.length); + + return arr[random].trim(); + + } +} diff --git a/common/src/main/java/com/imitate/common/sys/service/ResourceFileService.java b/common/src/main/java/com/imitate/common/sys/service/ResourceFileService.java new file mode 100644 index 0000000..debed8b --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/service/ResourceFileService.java @@ -0,0 +1,36 @@ +package com.imitate.common.sys.service; + +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class ResourceFileService { + + private final ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); + + public String getResourceFileContent(String name) { + String content = cacheMap.get(name); + if (content == null) { + content = this.readResourceFileContent(name); + cacheMap.put(name, content); + } + return content; + } + + private String readResourceFileContent(String name) { + InputStream in = null; + try { + in = this.getClass().getClassLoader().getResourceAsStream(name); + return IOUtils.toString(in, Charsets.toCharset("UTF-8")); + } catch (Exception e) { + throw new RuntimeException("读取资源文件" + name + "失败", e); + } finally { + IOUtils.closeQuietly(in); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/sys/service/SysConfigService.java b/common/src/main/java/com/imitate/common/sys/service/SysConfigService.java new file mode 100644 index 0000000..78a5e12 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/service/SysConfigService.java @@ -0,0 +1,670 @@ +package com.imitate.common.sys.service; + + +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.mapper.SysConfigMapper; +import com.imitate.common.sys.pojo.SysConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.ThreadUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; + +@Service +@Slf4j +public class SysConfigService { + + private volatile Map sysConfigMap = new HashMap<>(); + + @Autowired + private SysConfigMapper sysConfigMapper; + + + public void refreshSysConfig() { + Map sysConfigMapTemp = new HashMap<>(); + List sysConfigs = sysConfigMapper.selectAllSysConfig(); + for (SysConfig sysConfig : sysConfigs) { + sysConfigMapTemp.put(sysConfig.getName(), sysConfig.getVal()); + } + sysConfigMap = sysConfigMapTemp; + } + + /** + * 释放锁 + * + * @param param + */ + @Transactional + public void releasSysConfigLock(SysConfig param) { + SysConfig sysConfig = sysConfigMapper.selectSysConfigForUpdate(param.getName()); + if (sysConfig.getStatus().equals(SysConfigCsts.STATUS_DISABLE)) {// 已经被释放 + return; + } + boolean isSelf = sysConfig.getVal().equals(param.getVal()); + if (!isSelf) {// 非自己加的锁,不要释放 + return; + } + + sysConfig.setUpdateTime(LocalDateTime.now()); + sysConfig.setStatus(SysConfigCsts.STATUS_DISABLE); + sysConfigMapper.updateByPrimaryKey(sysConfig); + } + + + /** + * 如果锁定成功,返回 SysConfig对象, 否则返回 null + * + * @return + */ + @Transactional + public SysConfig lockSysConfig(SysConfig param) { + SysConfig sysConfig = sysConfigMapper.selectSysConfigForUpdate(param.getName()); + boolean expired = sysConfig.getExpireTime().compareTo(LocalDateTime.now()) < 0; + if (sysConfig.getStatus().equals(SysConfigCsts.STATUS_ENABLE) && !expired) {// 任务进行中,并且未过期 + return null; // 返回null,加锁不成功 + } + //param.setVal("172.16.94.254"); + + updateSysConfig(sysConfig, param); + + return sysConfig;// 返回记录,加锁成功 + } + + /** + * 该机器锁定时,刷新过期时间并返回 SysConfig对象, 否则返回 null + * + * @return + */ + @Transactional + public SysConfig refreshLockExpireTime(SysConfig param) { + SysConfig sysConfig = sysConfigMapper.selectSysConfigForUpdate(param.getName()); + boolean expired = sysConfig.getExpireTime().compareTo(LocalDateTime.now()) < 0; + if (sysConfig.getStatus().equals(SysConfigCsts.STATUS_ENABLE) && !expired + && sysConfig.getVal().equals(param.getVal())) {// 任务进行中,并且未过期 + updateSysConfig(sysConfig, param); + + return sysConfig;// 返回记录,刷新成功 + } + + return null; + } + + /** + * 带锁执行某个函数 + * @param f 函数 + * @param configParam 配置值 + * @param interval 执行间隔 + */ + public void loopExecuteInLock(Runnable f, SysConfig configParam, int interval) throws InterruptedException { + while (true) { + SysConfig sysConfig = null; + try { + sysConfig = lockSysConfig(configParam); + if (sysConfig != null) { + f.run(); + } + } catch (Throwable t) { + log.error("{}管理异常, value: {}", configParam.getName(), configParam.getVal(), t); + } finally { + if (sysConfig != null) { + try { + releasSysConfigLock(configParam); + } catch (Throwable t) { + log.error("{}管理释放锁异常, value: {}", configParam.getName(), configParam.getVal(), t); + } + } + } + + ThreadUtils.sleep(Duration.ofDays(interval)); + } + } + + public void updateSysConfig(SysConfig sysConfig, SysConfig param) { + + sysConfig.setVal(param.getVal()); + LocalDateTime now = LocalDateTime.now(); + sysConfig.setUpdateTime(now); + sysConfig.setStatus(SysConfigCsts.STATUS_ENABLE); + if (param.getExpireIn() != null) {// 参数中有过期时段,以此为准 + LocalDateTime expireTime = now.plusSeconds(param.getExpireIn()); + sysConfig.setExpireTime(expireTime); + } else if (sysConfig.getExpireIn() != null) {// 使用配置的过期时段 + LocalDateTime expireTime = now.plusSeconds(sysConfig.getExpireIn()); + sysConfig.setExpireTime(expireTime); + } + sysConfigMapper.updateByPrimaryKey(sysConfig); + } + + public SysConfig getSysConfigByName(String name) { + return sysConfigMapper.selectSysConfigByName(name); + } + + public void insertSysConfig(SysConfig sysConfig) { + sysConfigMapper.insertSelective(sysConfig); + } + + public void updateSysConfig(SysConfig sysConfig) { + sysConfigMapper.updateByPrimaryKeySelective(sysConfig); + } + + public int getShenlongOverloadPercent() { + String value = sysConfigMap.get(SysConfigCsts.SHENLONG_NODE_OVERLOAD_PERCENT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.SHENLONG_NODE_OVERLOAD_PERCENT_DEF; + } + + public int getGpuOverloadPercent() { + String value = sysConfigMap.get(SysConfigCsts.GPU_NODE_OVERLOAD_PERCENT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.GPU_NODE_OVERLOAD_PERCENT_DEF; + } + + public List getCommonImageNames() { + String value = sysConfigMap.get(SysConfigCsts.COMMON_IMAGE_NAME); + if (StringUtils.isNotEmpty(value)) { + return Arrays.asList(value.split(",")); + } + return SysConfigCsts.COMMON_IMAGE_NAME_DEF; + } + + public long getCreatePodCostMaxTime() { + String value = sysConfigMap.get(SysConfigCsts.CREATE_POD_COST_MAX_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.CREATE_POD_COST_MAX_TIME_DEF; + } + + public long getNodeOverloadExpireTime() { + String value = sysConfigMap.get(SysConfigCsts.NODE_OVERLOAD_EXPIRE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.NODE_OVERLOAD_EXPIRE_TIME_DEF; + } + + public long getShenlongNodeOverloadExpireTime() { + String value = sysConfigMap.get(SysConfigCsts.SHENLONG_NODE_OVERLOAD_EXPIRE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.SHENLONG_NODE_OVERLOAD_EXPIRE_TIME_DEF; + } + + public long getGpuNodeOverloadExpireTime() { + String value = sysConfigMap.get(SysConfigCsts.GPU_NODE_OVERLOAD_EXPIRE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.GPU_NODE_OVERLOAD_EXPIRE_TIME_DEF; + } + + public boolean getGpuNodeOpenReuse() { + String value = sysConfigMap.get(SysConfigCsts.GPU_NODE_OPEN_REUSE); + if (StringUtils.isNotEmpty(value)) { + return Boolean.parseBoolean(value); + } + return SysConfigCsts.GPU_NODE_OPEN_REUSE_DEF; + } + + public int getPodNoSchedulableWeight() { + String value = sysConfigMap.get(SysConfigCsts.POD_NO_SCHEDULABLE_WEIGHT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.POD_NO_SCHEDULABLE_WEIGHT_DEF; + } + + public int getVncPodSurvivalSecond() { + String value = sysConfigMap.get(SysConfigCsts.VNC_POD_SURVIVAL_SECOND); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.VNC_POD_SURVIVAL_SECOND_DEF; + } + + public int getVncPodDelaySecond() { + String value = sysConfigMap.get(SysConfigCsts.VNC_POD_DELAY_SECOND); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.VNC_POD_DELAY_SECOND_DEF; + } + + public int getOjNodeMaxPowPercent() { + String value = sysConfigMap.get(SysConfigCsts.OJ_NODE_MAX_POW_PERCENT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.OJ_NODE_MAX_POW_PERCENT_DEF; + } + + public long getOjNodeOverloadExpireTime() { + String value = sysConfigMap.get(SysConfigCsts.OJ_NODE_OVERLOAD_EXPIRE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.OJ_NODE_OVERLOAD_EXPIRE_TIME_DEF; + } + + public int getOjNodeMaxEvaTimeoutNum() { + String value = sysConfigMap.get(SysConfigCsts.OJ_NODE_MAX_EVA_TIMEOUT_NUM); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.OJ_NODE_MAX_EVA_TIMEOUT_NUM_DEF; + } + + public boolean getOjNodeOpenReuse() { + String value = sysConfigMap.get(SysConfigCsts.OJ_NODE_OPEN_REUSE); + if (StringUtils.isNotEmpty(value)) { + return Boolean.parseBoolean(value); + } + return SysConfigCsts.OJ_NODE_OPEN_REUSE_DEF; + } + + public int getNodeCpuMaxUsageRate() { + String value = sysConfigMap.get(SysConfigCsts.NODE_CPU_MAX_USAGE_RATE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NODE_CPU_MAX_USAGE_RATE_DEF; + } + + public int getNodeCpuMinUsageRate() { + String value = sysConfigMap.get(SysConfigCsts.NODE_CPU_MIN_USAGE_RATE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NODE_CPU_MIN_USAGE_RATE_DEF; + } + + public double getStatefulNotScaleClusterRequestCpuRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATEFUL_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATEFUL_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF; + } + + public double getStatefulNotScaleClusterRequestMemoryRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATEFUL_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATEFUL_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF; + } + + public double getStatefulScaleClusterRequestCpuRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATEFUL_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATEFUL_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF; + } + + public double getStatefulScaleClusterRequestMemoryRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATEFUL_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATEFUL_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF; + } + + public double getStatelessScaleClusterRequestCpuRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATELESS_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATELESS_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF; + } + + public double getStatelessScaleClusterRequestMemoryRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATELESS_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATELESS_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF; + } + + public double getStatelessNotScaleClusterRequestCpuRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATELESS_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATELESS_NOT_SCALE_CLUSTER_REQUEST_CPU_RATIO_LIMIT_DEF; + } + + public double getStatelessNotScaleClusterRequestMemoryRatioLimit() { + String value = sysConfigMap.get(SysConfigCsts.STATELESS_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.STATELESS_NOT_SCALE_CLUSTER_REQUEST_MEMORY_RATIO_LIMIT_DEF; + } + + public String getRemoteClusterGitHost() { + return sysConfigMap.get(SysConfigCsts.REMOTE_CLUSTER_GIT_HOST); + } + + public String getHideNFSInfoCommand() { + return sysConfigMap.get(SysConfigCsts.HIDE_NFS_INFO_COMMAND); + } + + public boolean getBridgeCenterCloseEva() { + String value = sysConfigMap.get(SysConfigCsts.BRIDGE_CENTER_CLOSE_EVA); + if (StringUtils.isNotEmpty(value)) { + return Boolean.parseBoolean(value); + } + return SysConfigCsts.BRIDGE_CENTER_CLOSE_EVA_DEF; + } + + public int getK8sMinUsablePort() { + String value = sysConfigMap.get(SysConfigCsts.K8S_USABLE_PORT_RANGE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value.split(",")[0]); + } + return SysConfigCsts.K8S_MIN_USABLE_PORT_DEF; + } + + public int getK8sMaxUsablePort() { + String value = sysConfigMap.get(SysConfigCsts.K8S_USABLE_PORT_RANGE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value.split(",")[1]); + } + return SysConfigCsts.K8S_MAX_USABLE_PORT_DEF; + } + + public long getDeletePodRedisExpireTime() { + String value = sysConfigMap.get(SysConfigCsts.DELETE_POD_REDIS_EXPIRE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Long.parseLong(value); + } + return SysConfigCsts.DELETE_POD_REDIS_EXPIRE_TIME_DEF; + } + + public int getScaleNodeWeight() { + String value = sysConfigMap.get(SysConfigCsts.SCALE_NODE_WEIGHT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.SCALE_NODE_WEIGHT_DEF; + } + + + public String getImageNodeSelectKey(String containers) { + List imagesInfo; + String value = sysConfigMap.get(SysConfigCsts.CONTAINER_IMAGE_TYPE); + if (StringUtils.isNotEmpty(value)) { + imagesInfo = Arrays.asList(value.split(",")); + } else { + imagesInfo = SysConfigCsts.CONTAINER_IMAGE_TYPE_DEF; + } + for (String image : imagesInfo) { + String imageType = image.split("_")[0]; + if (containers.contains(imageType)) { + return image.split("_")[1]; + } + } + return null; + } + + public List getNodeTypeLabels() { + List nodeInfo; + String value = sysConfigMap.get(SysConfigCsts.CONTAINER_IMAGE_TYPE); + if (StringUtils.isNotEmpty(value)) { + nodeInfo = Arrays.asList(value.split(",")); + } else { + nodeInfo = SysConfigCsts.CONTAINER_IMAGE_TYPE_DEF; + } + List nodeTypeLabels = new ArrayList<>(nodeInfo.size()); + for (String info : nodeInfo) { + String nodeTypeLabel = info.split("_")[1]; + nodeTypeLabels.add(nodeTypeLabel); + } + return nodeTypeLabels; + } + + public double getNodeEvaErrorMinRatio() { + String value = sysConfigMap.get(SysConfigCsts.NODE_EVA_ERROR_MIN_RATIO); + if (StringUtils.isNotEmpty(value)) { + return Double.parseDouble(value); + } + return SysConfigCsts.NODE_EVA_ERROR_MIN_RATIO_DEF; + } + + public int getNodeEvaErrorCheckTime() { + String value = sysConfigMap.get(SysConfigCsts.NODE_EVA_ERROR_CHECK_TIME); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NODE_EVA_ERROR_CHECK_TIME_DEF; + } + + public int getOjPodNumToCpuPercentage() { + String value = sysConfigMap.get(SysConfigCsts.OJ_POD_NUM_TO_CPU_PERCENTAGE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.OJ_POD_NUM_TO_CPU_PERCENTAGE_DEF; + } + + public String getRemoteConnPrefix() { + String value = sysConfigMap.get(SysConfigCsts.REMOTE_CONN_PREFIX); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.REMOTE_CONN_PREFIX_DEF; + } + + public String getK8sImagePullSecret() { + String value = sysConfigMap.get(SysConfigCsts.K8S_IMAGE_PULL_SECRET); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.K8S_IMAGE_PULL_SECRET_DEF; + } + + public String getK8sImagePullUrl() { + String value = sysConfigMap.get(SysConfigCsts.K8S_IMAGE_PULL_URL); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.K8S_IMAGE_PULL_URL_DEF; + } + + public boolean getOpenNodeStatusCheck() { + String value = sysConfigMap.get(SysConfigCsts.OPEN_NODE_STATUS_CHECK); + if (StringUtils.isNotEmpty(value)) { + return Boolean.parseBoolean(value); + } + return SysConfigCsts.OPEN_NODE_STATUS_CHECK_DEF; + } + + public Integer getNewCapacityNodeWeight() { + String value = sysConfigMap.get(SysConfigCsts.NEW_CAPACITY_NODE_WEIGHT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NEW_CAPACITY_NODE_WEIGHT_DEF; + } + + public Integer getNewCapacityNodePrepareTime() { + String value = sysConfigMap.get(SysConfigCsts.NEW_CAPACITY_NODE_PREPARE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NEW_CAPACITY_NODE_PREPARE_TIME_DEF; + } + + public String getCreatePodFlowControl() { + String value = sysConfigMap.get(SysConfigCsts.CREATE_POD_FLOW_CONTROL); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.CREATE_POD_FLOW_CONTROL_DEF; + } + + public Integer getNewCapacityNodeTime() { + String value = sysConfigMap.get(SysConfigCsts.NEW_CAPACITY_NODE_TIME); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NEW_CAPACITY_NODE_TIME_DEF; + } + + public Integer getNodeMinPodNum() { + String value = sysConfigMap.get(SysConfigCsts.NODE_MIN_POD_NUM); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NODE_MIN_POD_NUM_DEF; + } + + public Integer getPreReduceNodeWeight() { + String value = sysConfigMap.get(SysConfigCsts.PRE_REDUCE_NODE_WEIGHT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.PRE_REDUCE_NODE_WEIGHT_DEF; + } + + public String getLinuxShowServer() { + String value = sysConfigMap.get(SysConfigCsts.LINUX_SHOW_SERVER); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.LINUX_SHOW_SERVER_DEF; + } + + public Integer getCreatePodErrorRetryCount() { + String value = sysConfigMap.get(SysConfigCsts.CREATE_POD_ERROR_RETRY_COUNT); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.CREATE_POD_ERROR_RETRY_COUNT_DEF; + } + + public int getNodeEvaErrorMinNum() { + String value = sysConfigMap.get(SysConfigCsts.NODE_EVA_ERROR_MIN_NUM); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.NODE_EVA_ERROR_MIN_NUM_DEF; + } + + public Map getCloudHostNameConfig() { + Map config = new HashMap<>(); + String value = sysConfigMap.get(SysConfigCsts.CLOUD_HOST_NAME_CONFIG); + if (StringUtils.isNotEmpty(value)) { + List list = Arrays.asList(value.split(",")); + for (String str : list) { + config.put(str.split("_")[0], str.split("_")[1]); + } + } + return config; + } + + public List getNotScaleNodeTimeHour() { + List values = new ArrayList<>(); + String value = sysConfigMap.get(SysConfigCsts.NOT_SCALE_NODE_TIME_HOUR); + if (StringUtils.isNotEmpty(value)) { + values = Arrays.asList(value.split(",")); + } + return values; + } + + public String getPushToTpiAddCmd() { + String value = sysConfigMap.get(SysConfigCsts.JUPYTER_LAB_PUSH_ADD_CMD); + if (StringUtils.isNotEmpty(value)) { + return value; + } + return SysConfigCsts.JUPYTER_LAB_PUSH_ADD_CMD_DEF; + } + + public int getForwardEntryCheckCode() { + String value = sysConfigMap.get(SysConfigCsts.FORWARD_ENTRY_CHECK_CODE); + if (StringUtils.isNotEmpty(value)) { + return Integer.parseInt(value); + } + return SysConfigCsts.FORWARD_ENTRY_CHECK_CODE_DEF; + } + + /** + * 云主机开关 + */ + public boolean getCloudHostSwitch() { + String value = sysConfigMap.get(SysConfigCsts.CLOUD_HOST_SWITCH); + if (StringUtils.isNotEmpty(value)) { + return Boolean.parseBoolean(value); + } + return SysConfigCsts.CLOUD_HOST_SWITCH_DEF; + } + + public int getVscodePodDelaSecond(){ + String value = sysConfigMap.get(SysConfigCsts.VSCODE_POD_DELAY_SECOND); + if(StringUtils.isNotEmpty(value)){ + return Integer.parseInt(value); + } + return SysConfigCsts.VSCODE_POD_DELAY_SECOND_DEF; + } + + public int getHPAMinReplica(){ + String value = sysConfigMap.get(SysConfigCsts.HPA_MIN_REPLICA); + if(StringUtils.isNotEmpty(value)){ + return Integer.parseInt(value); + } + return SysConfigCsts.HPA_MIN_REPLICA_DEF; + } + + public int getHPAMaxReplica(){ + String value = sysConfigMap.get(SysConfigCsts.HPA_MAX_REPLICA); + if(StringUtils.isNotEmpty(value)){ + return Integer.parseInt(value); + } + return SysConfigCsts.HPA_MAX_REPLICA_DEF; + } + + public int getHPADesireCpuPercent(){ + String value = sysConfigMap.get(SysConfigCsts.HPA_DESIRE_CPU_PERCENT); + if(StringUtils.isNotEmpty(value)){ + return Integer.parseInt(value); + } + return SysConfigCsts.HPA_DESIRE_CPU_PERCENT_DEF; + } + + public int getHPADesireMemoryPercent(){ + String value = sysConfigMap.get(SysConfigCsts.HPA_DESIRE_MEMORY_PERCENT); + if(StringUtils.isNotEmpty(value)){ + return Integer.parseInt(value); + } + return SysConfigCsts.HPA_DESIRE_MEMORY_PERCENT_DEF; + } + + public List getLongLiveImage() { + List values = new ArrayList<>(); + String value = sysConfigMap.get(SysConfigCsts.LONG_LIVE_IMAGE); + if (StringUtils.isNotEmpty(value)) { + values = Arrays.asList(value.split(",")); + } + return values; + } + + public List getLongLivePod() { + List values = new ArrayList<>(); + String value = sysConfigMap.get(SysConfigCsts.LONG_LIVE_POD); + if (StringUtils.isNotEmpty(value)) { + values = Arrays.asList(value.split(",")); + } + return values; + } +} diff --git a/common/src/main/java/com/imitate/common/sys/settings/AppConfig.java b/common/src/main/java/com/imitate/common/sys/settings/AppConfig.java new file mode 100644 index 0000000..e6271c3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/sys/settings/AppConfig.java @@ -0,0 +1,436 @@ +package com.imitate.common.sys.settings; + +import com.imitate.common.util.IpUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource(value = { "classpath:cron.properties" }) +public class AppConfig { + + @Value("${educoderURL}") + private String educoderURL; + + @Value("${ojEvaCallBackURL}") + private String ojEvaCallBackURL; + + @Value("${simpileEducoderURL}") + private String simpileEducoderURL; + + @Value("${workspace}") + private String workspace; + @Value("${datadir}") + private String datadir; + @Value("${bigDataDir}") + private String bigDataDir; + @Value("${testCaseDir}") + private String testCaseDir; + + @Value("${maxRunningPodNum}") + private String maxRunningPodNum; + + @Value("${defaultTimeLimit}") + private String defaultTimeLimit; + + @Value("${dockerMaster}") + private String dockerMaster; + @Value("${dockerDeputies}") + private String dockerDeputies; + @Value("${registry}") + private String registry; + + @Value("${cpuAllocatable}") + private String cpuAllocatable; + @Value("${memAllocatable}") + private String memAllocatable; + + @Value("${gitIP}") + private String gitIP; + + @Value("${gitBackUpIP}") + private String gitBackUpIP; + + @Value("${gitPort}") + private String gitPort; + @Value("${gitBackupDirs}") + private String gitBackupDirs; + @Value("${gitRepoDir}") + private String gitRepoDir; + + @Value("${outputSize}") + private Integer outputSize; + + @Value("${secretKey}") + private String secretKey; + + @Value("${gpuImage}") + private String gpuImage; + + @Value("${shenlong.image.character}") + private String shenlongImageCharacter; + + @Value("${gitUsername}") + private String gitUsername; + + @Value("${gitPassword}") + private String gitPassword; + + @Value("${vncPort}") + private Integer vncPort; + + @Value("${jupyterPort}") + private Integer jupyterPort; + @Value("${jupyterSurvivalMinute}") + private Integer jupyterSurvivalMinute; + @Value("${tpmJupyterSurvivalMinute}") + private Integer tpmJupyterSurvivalMinute; + @Value("${jupyterDelayMinute}") + private Integer jupyterDelayMinute; + @Value("${tpmJupyterDelayMinute}") + private Integer tpmJupyterDelayMinute; + + @Value("${netNames}") + private String netNames; + + private String bridgeInstanceName; + + @Value("${webssh.delayMinute}") + private Integer websshDelayMinute; + + @Value("${gitServiceDomain}") + private String gitServiceDomain; + + @Value("${eva.MaxPoolSize}") + private Integer evaMaxPoolSize; + + @Value("${eva.HttpMaxConnTotal}") + private Integer evaHttpMaxConnTotal; + + @Value("${eva.HttpMaxConnPerRoute}") + private Integer evaHttpMaxConnPerRoute; + + @Value("${git.HttpMaxConnTotal}") + private Integer gitHttpMaxConnTotal; + + @Value("${git.HttpMaxConnPerRoute}") + private Integer gitHttpMaxConnPerRoute; + + @Value("${k8s.MaxConcurrentRequests}") + private Integer k8sMaxConcurrentRequests; + + @Value("${k8s.MaxConcurrentRequestsPerHost}") + private Integer k8sMaxConcurrentRequestsPerHost; + + @Value("${windows.forwardTableId}") + private String windowsForwardTableId; + + @Value("${windows.externalIp}") + private String windowsExternalIp; + + @Value("${windows.username}") + private String windowsUsername; + + @Value("${linux.username}") + private String linuxUsername; + + @Value("${windows.password}") + private String windowsPassword; + + @Value("${windows.workspace}") + private String windowsWorkspace; + + @Value("${windows.initTime}") + private Integer windowsInitTime; + + @Value("${windows.delayTime}") + private Integer windowsDelayTime; + + @Value("${sophgo.delayTime}") + private Integer sophgoDelayTime; + + @Value("${evaluate.pod.pass.delayDeleteTime}") + private Integer evaluatePodPassDelayDeleteTime; + + @Value("${evaluate.pod.default.delayDeleteTime}") + private Integer evaluatePodDefaultDelayDeleteTime; + + @Value("${vscodeDomain}") + private String vscodeDomain; + + @Value("${sophgo.apiKey}") + private String sophgoApiKey; + + @Value("${sophgo.apiSecret}") + private String sophgoApiSecret; + + @Value("${sophgo.api.domain}") + private String sophgoApiDomain; + + @Value("${sophgo.api.notifyUrl}") + private String sophgoApiNotifyUrl; + + @Value("${workspace.nfs.configs}") + private String workspaceNFSConfigs; + + public Integer getSophgoDelayTime() { + return sophgoDelayTime; + } + + public void setSophgoDelayTime(Integer sophgoDelayTime) { + this.sophgoDelayTime = sophgoDelayTime; + } + + public String getSophgoApiNotifyUrl() { + return sophgoApiNotifyUrl; + } + + public void setSophgoApiNotifyUrl(String sophgoApiNotifyUrl) { + this.sophgoApiNotifyUrl = sophgoApiNotifyUrl; + } + + public String getSophgoApiDomain() { + return sophgoApiDomain; + } + + public void setSophgoApiDomain(String sophgoApiDomain) { + this.sophgoApiDomain = sophgoApiDomain; + } + + public String getSophgoApiKey() { + return sophgoApiKey; + } + + public void setSophgoApiKey(String sophgoApiKey) { + this.sophgoApiKey = sophgoApiKey; + } + + public String getSophgoApiSecret() { + return sophgoApiSecret; + } + + public void setSophgoApiSecret(String sophgoApiSecret) { + this.sophgoApiSecret = sophgoApiSecret; + } + + public String getEducoderURL() { + return educoderURL; + } + + public String getOjEvaCallBackURL() { + return ojEvaCallBackURL; + } + + public String getSimpileEducoderURL() { + return simpileEducoderURL; + } + + public String getWorkspace() { + return workspace; + } + + public String getDatadir() { + return datadir; + } + + public String getMaxRunningPodNum() { + return maxRunningPodNum; + } + + public String getDefaultTimeLimit() { + return defaultTimeLimit; + } + + public String getCpuAllocatable() { + return cpuAllocatable; + } + + public String getMemAllocatable() { + return memAllocatable; + } + + public String getGitBackUpIP() { + return gitBackUpIP; + } + + public String getGitIP() { + return gitIP; + } + + public String getGitPort() { + return gitPort; + } + + public String getGitBackupDirs() { + return gitBackupDirs; + } + + public String getGitRepoDir() { + return gitRepoDir; + } + + public String getDockerMaster() { + return dockerMaster; + } + + public String getDockerDeputies() { + return dockerDeputies; + } + + public String getRegistry() { + return registry; + } + + public Integer getOutputSize() { + return outputSize; + } + + public String getSecretKey() { + return secretKey; + } + + public String getGpuImage() { + return gpuImage; + } + + public String getShenlongImageCharacter() { + return shenlongImageCharacter; + } + + public String getGitUsername() { + return gitUsername; + } + + public String getGitPassword() { + return gitPassword; + } + + public Integer getVncPort() { + return vncPort; + } + + public Integer getJupyterPort() { + return jupyterPort; + } + + public Integer getJupyterSurvivalMinute(String tpiID) { + if (tpiID.startsWith("tpm")) { + return tpmJupyterSurvivalMinute; + } + return jupyterSurvivalMinute; + } + + public Integer getJupyterDelayMinute(String tpiID) { + if (tpiID.startsWith("tpm")) { + return tpmJupyterDelayMinute; + } + return jupyterDelayMinute; + } + + public String getBridgeInstanceName() { + if (bridgeInstanceName == null) { + bridgeInstanceName = IpUtils.getLocalIpByName(netNames); + } + return bridgeInstanceName; + } + + public String getOjProjectDirname() { + return "code"; + } + + public Integer getWebsshDelayMinute() { + return websshDelayMinute; + } + + public String getGitServiceDomain() { + return gitServiceDomain; + } + + public Integer getEvaMaxPoolSize() { + return evaMaxPoolSize; + } + + public Integer getEvaHttpMaxConnTotal() { + return evaHttpMaxConnTotal; + } + + public Integer getEvaHttpMaxConnPerRoute() { + return evaHttpMaxConnPerRoute; + } + + public Integer getGitHttpMaxConnTotal() { + return gitHttpMaxConnTotal; + } + + public Integer getGitHttpMaxConnPerRoute() { + return gitHttpMaxConnPerRoute; + } + + public Integer getK8sMaxConcurrentRequests() { + return k8sMaxConcurrentRequests; + } + + public Integer getK8sMaxConcurrentRequestsPerHost() { + return k8sMaxConcurrentRequestsPerHost; + } + + public String getWindowsForwardTableId() { + return windowsForwardTableId; + } + + public String getWindowsExternalIp() { + return windowsExternalIp; + } + + public String getWindowsUsername() { + return windowsUsername; + } + + public String getLinuxUsername() { + return linuxUsername; + } + + public String getWindowsPassword() { + return windowsPassword; + } + + public String getWindowsWorkspace() { + return windowsWorkspace; + } + + public Integer getWindowsInitTime() { + return windowsInitTime; + } + + public Integer getWindowsDelayTime() { + return windowsDelayTime; + } + + public String getBigDataDir() { + return bigDataDir; + } + + public String getTestCaseDir() { + return testCaseDir; + } + + public String getVscodeDomain() { + return vscodeDomain; + } + + public void setVscodeDomain(String vscodeDomain) { + this.vscodeDomain = vscodeDomain; + } + + public Integer getEvaluatePodPassDelayDeleteTime() { + return evaluatePodPassDelayDeleteTime; + } + + public Integer getEvaluatePodDefaultDelayDeleteTime() { + return evaluatePodDefaultDelayDeleteTime; + } + + public String getWorkspaceNFSConfigs() { + return workspaceNFSConfigs; + } +} diff --git a/common/src/main/java/com/imitate/common/task/DelHisK8sNodeImageTask.java b/common/src/main/java/com/imitate/common/task/DelHisK8sNodeImageTask.java new file mode 100644 index 0000000..788a6ea --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/DelHisK8sNodeImageTask.java @@ -0,0 +1,90 @@ +package com.imitate.common.task; + +import com.imitate.common.bean.ShellResult; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.ShellUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 清理历史创建的镜像 + */ +@Component +public class DelHisK8sNodeImageTask { + private final static Logger logger = LoggerFactory.getLogger(DelHisK8sNodeImageTask.class); + + @Autowired + private SysConfigService sysConfigService; + + @Scheduled(cron = "${DelHisK8sNodeImageTask}") + public void work() { + logger.info("[start]定时任务: 清理k8s节点下载的镜像"); + String[] nodeIps = findK8sNodeIp(); + if (nodeIps == null || nodeIps.length <= 0) { + logger.info("[end]定时任务: 清理k8s节点下载的镜像"); + return; + } + for (String nodeIp : nodeIps) { + if (StringUtils.isNotEmpty(nodeIp)) { + List images = findNodeDeleteImages(nodeIp); + for (String image : images) { + deleteK8sNodeImage(nodeIp, image); + } + } + } + logger.info("[end]定时任务: 清理k8s节点下载的镜像"); + } + + private String[] findK8sNodeIp() { + String findCommand = "kubectl get node -o wide | grep -v master | grep -v STATUS | awk '{print $6}'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(findCommand); + String out = result.getOut(); + if (0 == result.getExitStatus() && StringUtils.isNotEmpty(out)) { + return out.split("\n"); + } + return null; + } + + private List findNodeDeleteImages(String nodeIp) { + List imageIds = new ArrayList<>(); + String remoteConnection = String.format(sysConfigService.getRemoteConnPrefix(), nodeIp); + String k8sImagePullUrl = sysConfigService.getK8sImagePullUrl(); + k8sImagePullUrl = k8sImagePullUrl.substring(0, k8sImagePullUrl.length() - 2); + String findCommand = remoteConnection + " \" docker images | grep "+ k8sImagePullUrl +"\""; + ShellResult result = ShellUtil.executeAndGetExitStatus(findCommand); + String out = result.getOut(); + if (0 == result.getExitStatus() && StringUtils.isNotEmpty(out)) { + String[] imageInfos = out.split("\n"); + for (String image : imageInfos) { + int time = 5; + while (time > 0) { + image = image.replaceAll(" ", " "); + time--; + } + if (image.split(" ").length >= 2) { + imageIds.add(image.split(" ")[2]); + } + } + } + return imageIds; + } + + private boolean deleteK8sNodeImage(String nodeIp, String image) { + String remoteConnection = String.format(sysConfigService.getRemoteConnPrefix(), nodeIp); + String deleteCommand = remoteConnection + " \" docker rmi "+ image +" \""; + ShellResult result = ShellUtil.executeAndGetExitStatus(deleteCommand); + if (0 == result.getExitStatus()) { + logger.info("删除节点镜像成功 nodeIp: {}, image: {}", nodeIp, image); + return true; + } + logger.error("删除节点镜像失败 nodeIp: {}, command: {}, error: {}", nodeIp, deleteCommand, result.getOut()); + return false; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/DelMissPodTask.java b/common/src/main/java/com/imitate/common/task/DelMissPodTask.java new file mode 100644 index 0000000..36b5a40 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/DelMissPodTask.java @@ -0,0 +1,104 @@ +package com.imitate.common.task; + +import com.imitate.common.config.DateConfig; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.TpUtils; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 清理遗漏pod + */ +@Component +public class DelMissPodTask { + + private final static Logger logger = LoggerFactory.getLogger(DelMissPodTask.class); + + private final static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter + .ofPattern(DateConfig.DEFAULT_DATE_TIME_FORMAT); + + @Autowired + private K8sService k8sService; + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private SysConfigService sysConfigService; + + @Scheduled(cron = "${DelMissPodTask}") + public void work() { + logger.info("[start]定时任务: 清理遗漏pod"); + for (String cluster : clusterManager.getClusterInfo().keySet()) { + clearClusterRes(cluster); + } + logger.info("[end]定时任务: 清理遗漏pod"); + } + + private void clearClusterRes(String cluster) { + logger.info("[start]定时任务: 清理遗漏pod, cluster: {}", cluster); + + List pods = k8sService.getPods(cluster); + for (Pod pod : pods) { + // 过滤掉oj pod + if (K8sUtils.isOjPod(pod)) { + continue; + } + // 过滤常驻镜像 + if (pod.getSpec().getContainers().stream().anyMatch(container -> sysConfigService.getLongLiveImage().contains(container.getImage().split(":")[0]))) { + continue; + } + // 过滤常驻pod + if (sysConfigService.getLongLivePod().contains(pod.getMetadata().getName())) { + continue; + } + + LocalDateTime startTime = k8sTimeConversion(pod.getMetadata().getCreationTimestamp()); + if (startTime.isBefore(LocalDateTime.now().plusHours(-2))) { + String podName = pod.getMetadata().getName(); + k8sService.deletePod(cluster, podName); + k8sService.deleteServiceByTpiId(cluster, TpUtils.getTpiID(podName)); + + logger.info("定时任务: 成功清理遗漏pod {}", podName); + } + } + + List services = k8sService.getServices(cluster); + for (Service service : services) { + LocalDateTime createTime = k8sTimeConversion(service.getMetadata().getCreationTimestamp()); + + if (createTime.isBefore(LocalDateTime.now().plusHours(-2))) { + String serviceName = service.getMetadata().getName(); + if (("kubernetes").equals(serviceName)) { + continue; + } + // pod存在的服务不删 + Pod svcPod = k8sService.getPod(cluster, service.getSpec().getSelector().get("name")); + if (svcPod != null && K8sUtils.isPodRunning(svcPod)) { + continue; + } + k8sService.deleteService(cluster, serviceName); + + logger.info("定时任务: 成功清理遗漏service {}", serviceName); + } + } + logger.info("[end]定时任务: 清理遗漏pod, cluster: {}", cluster); + } + + private LocalDateTime k8sTimeConversion(String timeStr) { + timeStr = timeStr.replace("T", " ").replace("Z", ""); + return LocalDateTime.parse(timeStr, DATE_TIME_FORMATTER).plusHours(8); + } +} diff --git a/common/src/main/java/com/imitate/common/task/init/DelExpiredPodTask.java b/common/src/main/java/com/imitate/common/task/init/DelExpiredPodTask.java new file mode 100644 index 0000000..6bda38a --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/init/DelExpiredPodTask.java @@ -0,0 +1,117 @@ +package com.imitate.common.task.init; + +import com.imitate.common.bean.ShellResult; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.RunPodQueryParam; +import com.imitate.common.k8s.pojo.RunPod; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.pojo.SysConfig; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.ShellUtil; +import com.imitate.common.util.ThreadUtils; +import com.imitate.common.util.TimeHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.util.List; + +/** + * pod生命周期管理 + */ +@Service +public class DelExpiredPodTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private SysConfigService sysConfigService; + @Autowired + private RunPodService runPodService; + @Autowired + private AppConfig appConfig; + @Autowired + private DiskService diskService; + + @PostConstruct + public void init() { + new Thread(this::doWork).start(); + } + + private void doWork() { + boolean hasHandle = false; + final SysConfig configParam = new SysConfig(); + configParam.setName(SysConfigCsts.CONFIG_POD_MANAGE); + configParam.setVal(appConfig.getBridgeInstanceName()); + while (true) { + hasHandle = false; + SysConfig sysConfig = null; + try { + sysConfig = sysConfigService.lockSysConfig(configParam); + if (sysConfig != null) { + + // 获得清理过期RunPod的锁,查询出过期列表,一一清理 + RunPodQueryParam param = new RunPodQueryParam(); + param.setExpireTime(LocalDateTime.now()); + List expireList = runPodService.getRunPods(param); + for (RunPod runPod : expireList) { + hasHandle = true; + try { + boolean deleted = runPodService.deteleRunPod(runPod); + // 主动删除的场景进行善后工作 + if (deleted) { + afterPodDeleted(runPod); + } + } catch (Exception e) { + logger.error("删除过期pod:" + runPod.getName(), e); + } + } + + } + } catch (Throwable t) { + logger.error("管理pod 异常", t); + } finally { + if (sysConfig != null) { + try { + sysConfigService.releasSysConfigLock(configParam); + } catch (Throwable t) { + logger.error("释放pod管理锁异常", t); + } + } + } + + if (!hasHandle) { + ThreadUtils.sleep(1000); + } + } + } + + /** + * Pod删除后的特殊逻辑处理 + * @param runPod + */ + private void afterPodDeleted(RunPod runPod) { + if (runPod.getImageName().contains("nodered")) { + String tpiID = runPod.getName().split("-")[1]; + String tpiRepoPath = diskService.buildTpiRepoPath(diskService.getTpiWorkspaceHostPath(tpiID), TpCsts.TP_UNIFY_REPO_NAME); + + // 执行push + String cmd = "cd " + tpiRepoPath + + " && git config user.email educoder@163.com && git config user.name educoder" + + " && git add ." + + " && git commit -m '" + TimeHelper.getCurrentTime() + "'" + " && git push -f origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + + if (result.getExitStatus() != 0) { + logger.error("删除pod后push本地变更到远端失败, tpiId: {}, cmd: {}, error: {}", tpiID, cmd, result.getOut()); + } else { + logger.info("删除pod后push本地变更到远端成功! tpiId: {}, cmd: {}", tpiID, cmd); + } + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/ClusterTask.java b/common/src/main/java/com/imitate/common/task/refresh/ClusterTask.java new file mode 100644 index 0000000..a9cb956 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/ClusterTask.java @@ -0,0 +1,56 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.mgr.NodeManager; +import com.imitate.common.k8s.util.K8sClientUtil; +import com.imitate.common.sys.service.ClusterConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * 刷新集群配置信息 + */ +@Component +public class ClusterTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private NodeManager nodeManager; + + @PostConstruct + public void init() { + refreshClusterConfigs(); + refreshClusters(); + } + + @Scheduled(cron = "${ClusterConfigTask}") + public void refreshClusterConfigs() { + try { + clusterConfigService.refreshClusterConfigs(); + K8sClientUtil.refreshK8sClients(); + } catch (Exception e) { + logger.error("刷新ClusterConfig出错", e); + } + } + + @Scheduled(cron = "${ClusterTask}") + public void refreshClusters() { + try { + clusterManager.refreshClusters(); + nodeManager.check(); + } catch (Exception e) { + logger.error("刷新集群信息出错", e); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/NodeErrorCheckTask.java b/common/src/main/java/com/imitate/common/task/refresh/NodeErrorCheckTask.java new file mode 100644 index 0000000..08a632c --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/NodeErrorCheckTask.java @@ -0,0 +1,150 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.NodeEvaErrorInfo; +import com.imitate.common.k8s.service.BridgePodService; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.competitor.LockCompetitor; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import io.fabric8.kubernetes.api.model.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +/** + * k8s节点状态检查 + * @author zmr + */ +@Component +public class NodeErrorCheckTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final static String EVA_ERROR_NODE_LABEL_VALUE = "evaError"; + + @Autowired + private BridgePodService bridgePodService; + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private K8sService k8sService; + + @Autowired + private RunPodService runPodService; + + @Qualifier("podScheduleLockCompetitor") + @Autowired + private LockCompetitor lockCompetitor; + + // @PostConstruct + public void init() { + refreshNodeErrorCheck(); + } + + // @Scheduled(cron = "${RefreshNodeErrorCheck}") + public void refreshNodeErrorCheck() { + try { + if (!lockCompetitor.winLock()) { + return; + } + evaErrorNodeCheck(); + } catch (Exception e) { + logger.error("刷新k8s节点状态出错", e); + } + } + + private void evaErrorNodeCheck() { + try { + int evaErrorCheckTime = sysConfigService.getNodeEvaErrorCheckTime(); + if (evaErrorCheckTime <= 0) { + return; + } + + List clusters = clusterConfigService.getAvailableClusters(); + int errorNum = sysConfigService.getNodeEvaErrorMinNum(); + double errorRatio = sysConfigService.getNodeEvaErrorMinRatio(); + + LocalDateTime requestTime = LocalDateTime.now().plus(-evaErrorCheckTime, ChronoUnit.MILLIS); + List nodeEvaErrorInfos = bridgePodService.getNodeEvaErrorInfos(requestTime); + for (NodeEvaErrorInfo nodeEvaErrorInfo : nodeEvaErrorInfos) { + if (nodeEvaErrorInfo.getRunErrorNum() > 5 || nodeEvaErrorInfo.getCreatePodErrorNum() > 5) { + logger.info("评测错误节点检查 nodeEvaErrorInfo: {}, errorNum: {}, errorRatio: {}", nodeEvaErrorInfo, errorNum, errorRatio); + } + if (!nodeEvaErrorInfo.isErrorNode(errorNum, errorRatio)) { + // 新增代码: + //节点不为错误状态,将错误标签type=evaError恢复为type=others + String nodeName = nodeEvaErrorInfo.getNodeName(); + for (String cluster : clusters) { + errorNodeRecoverHandle(cluster, nodeName); + } + continue; + } + String nodeName = nodeEvaErrorInfo.getNodeName(); + boolean isRunError = nodeEvaErrorInfo.isRunErrorNode(errorNum, errorRatio); + + // 由于bridgePod模型没有cluster字段,暂时只能轮询处理 + for (String cluster : clusters) { + errorNodeHandle(cluster, nodeName, isRunError); + } + } + } catch (Exception e) { + logger.error("评测错误节点检查出错", e); + } + } + + private void errorNodeHandle(String cluster, String nodeName, Boolean isRunError) { + Node node = k8sService.getNode(cluster, nodeName); + if (node == null) { + return; + } + // 修改标签 + String oldLabelValue = K8sUtils.getLabel(node, TpCsts.NODE_TYPE); + if (!EVA_ERROR_NODE_LABEL_VALUE.equals(oldLabelValue)) { + // 设标签为type=evaError + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.NODE_TYPE, EVA_ERROR_NODE_LABEL_VALUE); + logger.info("长时间评测错误节点处理,修改标签 cluster: {}, nodeName: {}", cluster, nodeName); + } + + if (isRunError) { + // 移除pod + runPodService.deleteRunPodByNodeIp(cluster, K8sUtils.getIp(node)); + logger.info("长时间评测错误节点处理,移除pod cluster: {}, nodeName: {}", cluster, nodeName); + } + } + + /** + * @author huqifeng + * @Time 2021-7-12 + * @Description 当结点不再处于evaError状态时,用于将节点的标签type=evaError恢复为type=others + * @param cluster + * @param nodeName 节点node名称 + */ + private void errorNodeRecoverHandle(String cluster, String nodeName) { + Node node = k8sService.getNode(cluster, nodeName); + if (node == null) { + return; + } + // 将标签type=evaError恢复为type=others + String oldLabelValue = K8sUtils.getLabel(node, TpCsts.NODE_TYPE); + if (EVA_ERROR_NODE_LABEL_VALUE.equals(oldLabelValue)) { + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.NODE_TYPE, TpCsts.NODE_TYPE_OTHERS); + logger.info("长时间评测错误节点已恢复为正常状态,恢复标签type=others cluster: {}, nodeName: {}", cluster, nodeName); + //如果恢复了type=others标签,钉钉报警 +// String message = "k8s节点:"+nodeName+",不再处于错误状态,已恢复type=others标签,请查看恢复标签是否成功"; + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/NodeStatusCheckTask.java b/common/src/main/java/com/imitate/common/task/refresh/NodeStatusCheckTask.java new file mode 100644 index 0000000..f973ba8 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/NodeStatusCheckTask.java @@ -0,0 +1,275 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.bean.NodeQueryParam; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.competitor.LockCompetitor; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.JedisUtil; +import io.fabric8.kubernetes.api.model.Node; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.time.LocalDateTime; +import java.util.*; + +/** + * k8s节点状态检查 + */ +@Component +public class NodeStatusCheckTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private K8sService k8sService; + + @Qualifier("podScheduleLockCompetitor") + @Autowired + private LockCompetitor lockCompetitor; + + @PostConstruct + public void init() { + refreshNodeStatus(); + } + + @Scheduled(cron = "${RefreshNodeStatus}") + public void refreshNodeStatus() { + if (!lockCompetitor.winLock()) { + return; + } + List clusters = clusterConfigService.getAvailableClusters(); + for (String cluster : clusters) { + try { + // 获取集群所有节点 + NodeQueryParam param = new NodeQueryParam(); + param.setCluster(cluster); + List nodes = k8sService.getNodes(param); + List newCapacityNodes = new ArrayList<>(); + boolean allNewCapacityNodesOk = true; + + for (Node node : nodes) { + // 新增节点逻辑处理 + nodeNewCapacityLabelHandle(cluster, node); + Integer newNodePrepareTime = sysConfigService.getNewCapacityNodePrepareTime(); + if (newNodePrepareTime > 0 && K8sUtils.isNewCapacityNode(node, newNodePrepareTime + 3)) { + newCapacityNodes.add(node); + if (K8sUtils.isNewCapacityNode(node, newNodePrepareTime)) { + allNewCapacityNodesOk = false; + } + } else { + // 节点ready状态判断及处理 + nodeStatusHandle(cluster, node); + } + } + if (allNewCapacityNodesOk && newCapacityNodes.size() > 0) { + for (Node node : newCapacityNodes) { + nodeStatusHandle(cluster, node); + } + } + } catch (Exception e) { + logger.error("刷新k8s节点状态出错, cluster: {}", cluster, e); + } + } + + } + + @Scheduled(cron = "${RefreshNodePreReduce}") + public void refreshCapacityNodePreReduce() { + try { + if (!lockCompetitor.winLock()) { + return; + } + Map clusterMap = clusterManager.getClusterInfo(); + List clusters = clusterConfigService.getAvailableClusters(); + for (String cluster : clusters) { + ClusterInfo clusterInfo = clusterMap.get(cluster); + if (clusterInfo.getScaleNodeNum() <= 0) { + continue; + } + int podCount = clusterInfo.getPodCount(); + int needNodeNum = podCount / sysConfigService.getNodeMinPodNum() - clusterInfo.getForeverNodeNum(); + needNodeNum = needNodeNum < 0 ? 0 : needNodeNum; + List normalScaleNodes = new ArrayList<>(); + List preReduceNodes = new ArrayList<>(); + List reduceNodes = new ArrayList<>(); + Map nodeMap = clusterInfo.getNodeMap(); + for (Node node : nodeMap.values()) { + Map labelMap = node.getMetadata().getLabels(); + String txLabelValue = labelMap.get(TpCsts.K8S_ELASTIC_NODE_LABEL_KEY); + String statusLabelValue = labelMap.get(TpCsts.K8S_NODE_STATUS_LABEL_VALUE); + String typeLabelValue = labelMap.get(TpCsts.NODE_TYPE); + if (StringUtils.isNotEmpty(txLabelValue) && !TpCsts.NODE_TYPE_OTHERS.equals(typeLabelValue)) { + continue; + } + if (TpCsts.K8S_ELASTIC_NODE_LABEL_VALUE.equals(txLabelValue) + && TpCsts.K8S_NODE_STATUS_READY.equals(statusLabelValue)) { + if (TpCsts.NODE_TYPE_OTHERS.equals(typeLabelValue)) { + String preReduceLabelValue = K8sUtils.getLabel(node, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY); + if (StringUtils.isEmpty(preReduceLabelValue)) { + normalScaleNodes.add(node); + } else { + preReduceNodes.add(node); + } + } else { + boolean isSpecialNode = false; + List specialNodeLabels = sysConfigService.getNodeTypeLabels(); + specialNodeLabels.add(TpCsts.OJ_LABEL_KEY); + for (String specialLabel : specialNodeLabels) { + if ("true".equals(labelMap.get(specialLabel))) { + isSpecialNode = true; + break; + } + } + if (!isSpecialNode) { + reduceNodes.add(node); + } + } + } + } + if (preReduceNodes.size() > 0) { + JedisUtil.psetex(TpCsts.K8S_PRE_REDUCE_NODE_REDIS_KEY, "true", 12000L); + // 非缩容时间移除预缩容标签 + if (notScaleNodesTime()) { + for (Node node : preReduceNodes) { + String nodeName = K8sUtils.getNodeName(node); + k8sService.removeNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY); + logger.info("刷新k8s节点状态,移除预缩容标签 nodeName: {}", nodeName); + } + } + } + if (normalScaleNodes.size() == needNodeNum) { + continue; + } + // 减少正常节点,增加预缩容节点 + if (normalScaleNodes.size() > needNodeNum && !notScaleNodesTime()) { + int num = normalScaleNodes.size() - needNodeNum; + Collections.sort(normalScaleNodes, new Comparator() { + @Override + public int compare(Node o1, Node o2) { + return o1.getMetadata().getName().compareTo(o2.getMetadata().getName()); + } + }); + for (Node node : normalScaleNodes) { + String nodeName = K8sUtils.getNodeName(node); + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_VALUE); + logger.info("刷新k8s节点状态,新增预缩容标签 nodeName: {}", nodeName); + num--; + if (num <= 0) { + break; + } + } + } + // 增加正常节点,减少预缩容节点 + if (normalScaleNodes.size() < needNodeNum && (preReduceNodes.size() > 0 || reduceNodes.size() > 0)) { + int num = needNodeNum - normalScaleNodes.size(); + Collections.sort(preReduceNodes, new Comparator() { + @Override + public int compare(Node o1, Node o2) { + return -o1.getMetadata().getName().compareTo(o2.getMetadata().getName()); + } + }); + for (Node node : preReduceNodes) { + String nodeName = K8sUtils.getNodeName(node); + k8sService.removeNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY); + logger.info("刷新k8s节点状态,移除预缩容标签 nodeName: {}", nodeName); + num--; + if (num <= 0) { + break; + } + } + if (num > 0) { + Collections.sort(reduceNodes, new Comparator() { + @Override + public int compare(Node o1, Node o2) { + return -o1.getMetadata().getName().compareTo(o2.getMetadata().getName()); + } + }); + for (Node node : reduceNodes) { + String nodeName = K8sUtils.getNodeName(node); + k8sService.removeNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_PRE_REDUCE_LABEL_KEY); + logger.info("刷新k8s节点状态,缩容节点移除预缩容标签 nodeName: {}", nodeName); + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.NODE_TYPE, TpCsts.NODE_TYPE_OTHERS); + logger.info("刷新k8s节点状态,缩容节点增加type标签 nodeName: {}", nodeName); + num--; + if (num <= 0) { + break; + } + } + } + } + } + } catch (Exception e) { + logger.error("刷新k8s集群扩容节点预缩容标签信息出错", e); + } + } + + private boolean notScaleNodesTime() { + List notScaleNodeTimeHour = sysConfigService.getNotScaleNodeTimeHour(); + return notScaleNodeTimeHour.contains(Integer.toString(LocalDateTime.now().getHour())); + } + + /** + * 刷新几诶但ready状态 + * @param cluster 集群 + * @param node 节点 + */ + private void nodeStatusHandle(String cluster, Node node) { + String nodeName = K8sUtils.getNodeName(node); + String statusLabelValue = K8sUtils.getLabel(node, TpCsts.K8S_NODE_STATUS_LABEL_VALUE); + if (K8sUtils.isReadyStatus(node)) { + if (!TpCsts.K8S_NODE_STATUS_READY.equals(statusLabelValue)) { + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_STATUS_LABEL_VALUE, TpCsts.K8S_NODE_STATUS_READY); + logger.info("刷新k8s节点状态 nodeName: {} refreshStatus: {}", nodeName, TpCsts.K8S_NODE_STATUS_READY); + } + } else { + if (!TpCsts.K8S_NODE_STATUS_NOT_READY.equals(statusLabelValue)) { + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.K8S_NODE_STATUS_LABEL_VALUE, TpCsts.K8S_NODE_STATUS_NOT_READY); + logger.info("刷新k8s节点状态 nodeName: {} refreshStatus: {}", nodeName, TpCsts.K8S_NODE_STATUS_NOT_READY); + } + } + } + + /** + * 新增节点标签处理,取节点的创建时间,与数据库配置的 新增时间判定值 比较,小于新增标签,否则移除标签 + * @param cluster 集群 + * @param node 节点 + */ + private void nodeNewCapacityLabelHandle(String cluster, Node node) { + String nodeName = K8sUtils.getNodeName(node); + String newLabelValue = K8sUtils.getLabel(node, TpCsts.K8S_NEW_CAPACITY_NODE_LABEL_KEY); + Integer newCapacityNodeTime = sysConfigService.getNewCapacityNodeTime(); + if (newCapacityNodeTime > 0 && K8sUtils.isNewCapacityNode(node, newCapacityNodeTime)) { + if (StringUtils.isEmpty(newLabelValue)) { + String nodeNames = JedisUtil.get(TpCsts.K8S_NEW_CAPACITY_NODE_REDIS_KEY); + nodeNames = StringUtils.isEmpty(nodeNames) ? nodeName : nodeNames + "," + nodeName; + JedisUtil.psetex(TpCsts.K8S_NEW_CAPACITY_NODE_REDIS_KEY, nodeNames, (newCapacityNodeTime + 1) * 60000L); + k8sService.updateNodeLabel(cluster, nodeName, TpCsts.K8S_NEW_CAPACITY_NODE_LABEL_KEY, TpCsts.K8S_NEW_CAPACITY_NODE_LABEL_VALUE); + logger.info("刷新k8s节点状态,添加新增节点标签 nodeName: {}", nodeName); + } + } else { + if (StringUtils.isNotEmpty(newLabelValue)) { + k8sService.removeNodeLabel(cluster, nodeName, TpCsts.K8S_NEW_CAPACITY_NODE_LABEL_KEY); + logger.info("刷新k8s节点状态,移除新增节点标签 nodeName: {}", nodeName); + } + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/PlatformConfigTask.java b/common/src/main/java/com/imitate/common/task/refresh/PlatformConfigTask.java new file mode 100644 index 0000000..300dc36 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/PlatformConfigTask.java @@ -0,0 +1,36 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.k8s.service.PlatformConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * 刷新platform_config + */ +@Component +public class PlatformConfigTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private PlatformConfigService platformConfigService; + + @PostConstruct + public void init() { + refreshPlatformConfig(); + } + + @Scheduled(cron = "${RefreshPlatformConfigTask}") + public void refreshPlatformConfig() { + try { + platformConfigService.refreshPlatformConfig(); + } catch (Exception e) { + logger.error("加载PlatformConfig失败", e); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/ResUsageTask.java b/common/src/main/java/com/imitate/common/task/refresh/ResUsageTask.java new file mode 100644 index 0000000..0a5f5ae --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/ResUsageTask.java @@ -0,0 +1,36 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.k8s.service.NodeResUsageService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * 刷新资源使用率 + */ +@Component +public class ResUsageTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private NodeResUsageService nodeResUsageService; + + @PostConstruct + public void init() { + refreshNodeResUsage(); + } + + @Scheduled(cron = "${RefreshNodeResUsageTask}") + public void refreshNodeResUsage() { + try { + nodeResUsageService.refreshNodeResUsage(); + } catch (Exception e) { + logger.error("刷新k8s资源使用率出错", e); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/SecurityContextConfigTask.java b/common/src/main/java/com/imitate/common/task/refresh/SecurityContextConfigTask.java new file mode 100644 index 0000000..b644028 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/SecurityContextConfigTask.java @@ -0,0 +1,36 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.k8s.service.SecurityContextService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * 刷新SecurityContextConfig信息 + */ +@Component +public class SecurityContextConfigTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private SecurityContextService securityContextService; + + @PostConstruct + public void init() { + refreshSecurityContextConfig(); + } + + @Scheduled(cron = "${RefreshSecurityContextConfigTask}") + public void refreshSecurityContextConfig() { + try { + securityContextService.refreshSecurityContextConfigs(); + } catch (Exception e) { + logger.error("加载SecurityContextConfig出错", e); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/task/refresh/SysTask.java b/common/src/main/java/com/imitate/common/task/refresh/SysTask.java new file mode 100644 index 0000000..ddee011 --- /dev/null +++ b/common/src/main/java/com/imitate/common/task/refresh/SysTask.java @@ -0,0 +1,36 @@ +package com.imitate.common.task.refresh; + +import com.imitate.common.sys.service.SysConfigService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * 刷新系统信息 + */ +@Component +public class SysTask { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private SysConfigService sysConfigService; + + @PostConstruct + public void init() { + refreshSysConfig(); + } + + @Scheduled(cron = "${RefreshSysConfigTask}") + public void refreshSysConfig() { + try { + sysConfigService.refreshSysConfig(); + } catch (Exception e) { + logger.error("加载sys_config出错", e); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/test/MyStarterAutoConfiguration.java b/common/src/main/java/com/imitate/common/test/MyStarterAutoConfiguration.java new file mode 100644 index 0000000..e77362b --- /dev/null +++ b/common/src/main/java/com/imitate/common/test/MyStarterAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.imitate.common.test; + + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnWebApplication +@EnableConfigurationProperties(PersonConfigProperties.class) +public class MyStarterAutoConfiguration { + + @Bean("defaultStudent") + @ConditionalOnProperty(prefix = "mystarter.config", name = "enable", havingValue = "true") + public Person defaultStudent(PersonConfigProperties personConfigProperties) { + Person person = new Person(); + person.setAge(personConfigProperties.getAge()); + person.setName(personConfigProperties.getName()); + person.setGender(personConfigProperties.getGender()); + return person; + } + + + +} diff --git a/common/src/main/java/com/imitate/common/test/Person.java b/common/src/main/java/com/imitate/common/test/Person.java new file mode 100644 index 0000000..3ea73e6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/test/Person.java @@ -0,0 +1,11 @@ +package com.imitate.common.test; + + +import lombok.Data; + +@Data +public class Person { + private int age; + private String name; + private String gender; +} diff --git a/common/src/main/java/com/imitate/common/test/PersonConfigProperties.java b/common/src/main/java/com/imitate/common/test/PersonConfigProperties.java new file mode 100644 index 0000000..b4a55e3 --- /dev/null +++ b/common/src/main/java/com/imitate/common/test/PersonConfigProperties.java @@ -0,0 +1,13 @@ +package com.imitate.common.test; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties(prefix = "mystarter.config.student") +public class PersonConfigProperties { + private String name; + private int age; + private String gender; +} diff --git a/common/src/main/java/com/imitate/common/util/AbstractDO.java b/common/src/main/java/com/imitate/common/util/AbstractDO.java new file mode 100644 index 0000000..d8f641e --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/AbstractDO.java @@ -0,0 +1,34 @@ +package com.imitate.common.util; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Date; + +/** + * 表网关实体公共字段抽离DO抽象类 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +public abstract class AbstractDO implements Serializable{ + + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(generator = "JDBC") + private Long id; + + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8") + private LocalDateTime createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8") + private LocalDateTime updateTime; +} diff --git a/common/src/main/java/com/imitate/common/util/Base64Util.java b/common/src/main/java/com/imitate/common/util/Base64Util.java new file mode 100644 index 0000000..dba5a06 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/Base64Util.java @@ -0,0 +1,91 @@ +package com.imitate.common.util; + +import org.apache.commons.codec.binary.Base64; +import org.springframework.util.StringUtils; + +import java.nio.charset.StandardCharsets; + +/** + * Created by guange on 23/02/2017. + */ +public final class Base64Util { + + /** + * base64编码 + * + * @param code + * @return + */ + public static String encode(String code) { + byte[] encode = Base64.encodeBase64URLSafe(code.getBytes(StandardCharsets.UTF_8)); + return new String(encode, StandardCharsets.UTF_8); + } + + public static byte[] encodeBytes(byte[] codes) { + return Base64.encodeBase64(codes); + } + + /** + * base64解码 + * + * @param code + * @return + */ + public static String decode(String code) { + byte[] decode = Base64.decodeBase64(code); + return new String(decode, StandardCharsets.UTF_8); + } + + /** + * base64再解码,把原本的非URL safe编码转换为URL safe编码 + * + * @param code + * @return + */ + public static String reencode(String code) { + String str = decode(code).replace("\u0000", ""); + //str = str.replace("\n", "\r\n"); + str = replaceLineFeed(str); + return encode(str); + } + + private static String replaceLineFeed(String str) { + StringBuilder sb = new StringBuilder(); + char[] cs = str.toCharArray(); + int len = cs.length; + for (int i = 0; i < len; i++) { + char c = cs[i]; + if (c == '\n') { + if (i == 0) { + sb.append('\r').append(c); + } else { + char pre = cs[i - 1]; + if (pre == '\r') { + sb.append(c); + } else { + sb.append('\r').append(c); + } + } + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 正常编码 + * + * @param code + * @return + */ + public static String encodeNoSafe(String code) { + if (StringUtils.isEmpty(code)) { + code = " "; + } + byte[] encode = Base64.encodeBase64(code.getBytes(StandardCharsets.UTF_8)); + return new String(encode, StandardCharsets.UTF_8); + } + + +} diff --git a/common/src/main/java/com/imitate/common/util/BaseMapper.java b/common/src/main/java/com/imitate/common/util/BaseMapper.java new file mode 100644 index 0000000..4b78758 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/BaseMapper.java @@ -0,0 +1,13 @@ +package com.imitate.common.util; + +import com.imitate.common.util.AbstractDO; +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.MySqlMapper; + +/** + * 通用mapper整合桥梁 + * @author yanchao + */ +public interface BaseMapper extends Mapper, MySqlMapper { + // 特别注意,该接口不能被扫描到,否则会出错 +} diff --git a/common/src/main/java/com/imitate/common/util/BasePageCondition.java b/common/src/main/java/com/imitate/common/util/BasePageCondition.java new file mode 100644 index 0000000..0209876 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/BasePageCondition.java @@ -0,0 +1,52 @@ +package com.imitate.common.util; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 基础分页视图对象 + * @author yanchao + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class BasePageCondition implements Serializable { + + private static final long serialVersionUID = 1L; + + public final static int DEFAULT_PAGE_SIZE = 10; + + private Integer pageNumber; + private Integer pageSize; + private Integer pageStart; + + private String orderField; + private String orderDirection; + private String keywords; + + public BasePageCondition(){ + this.pageNumber = 1; + this.pageSize = 0; + this.pageStart = 0; + } + + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startDate; + + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endDate; + + public int getPageSize() { + return pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE; + } + + + public int getPageStart() { + return pageNumber > 0 ? (pageNumber - 1) * getPageSize() :0; + } +} diff --git a/common/src/main/java/com/imitate/common/util/BasicController.java b/common/src/main/java/com/imitate/common/util/BasicController.java new file mode 100644 index 0000000..727a27e --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/BasicController.java @@ -0,0 +1,34 @@ +package com.imitate.common.util; + +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.util.R; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; + +import java.util.List; + +/** + * @author yanchao + */ +public class BasicController { + public R actionResultWithBindingResult(ErrorCodeEnum errorCodeEnum, BindingResult bindingResult){ + String errMsg = getBindingResultErrors(bindingResult); + if(StringUtils.isBlank(errMsg)){ + return R.error(errorCodeEnum.getDescription()); + } + throw new BusinessException(errorCodeEnum.getValue(),errorCodeEnum.getDescription() + "," + errMsg); + } + public String getBindingResultErrors(BindingResult bindingResult){ + if (null == bindingResult || !bindingResult.hasErrors()) { + return null; + } + StringBuilder errorStrBuilder = new StringBuilder(); + List errorList = bindingResult.getAllErrors(); + for (ObjectError error : errorList) { + errorStrBuilder.append(error.getDefaultMessage()).append(";"); + } + return StringUtils.removeEnd(errorStrBuilder.toString(), ";"); + } +} diff --git a/common/src/main/java/com/imitate/common/util/FileUtil.java b/common/src/main/java/com/imitate/common/util/FileUtil.java new file mode 100644 index 0000000..a21a9e8 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/FileUtil.java @@ -0,0 +1,301 @@ +package com.imitate.common.util; + + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; + +/** + * Helper class to deal with end-of-line markers in text files. + *

+ * Loosely based on these examples: + * - http://stackoverflow.com/a/9456947/1084488 (cc by-sa 3.0) + * - http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/tomcat/buildutil/CheckEol.java (Apache License v2.0) + *

+ * This file is posted here to meet the "ShareAlike" requirement of cc by-sa 3.0: + * http://stackoverflow.com/a/27930311/1084488 + * + * @author Matthias Stevens + */ +public class FileUtil { + private static Logger logger = LoggerFactory.getLogger(FileUtil.class); + + /** + * Unix-style end-of-line marker (LF) + */ + private static final String EOL_UNIX = "\n"; + + /** + * Windows-style end-of-line marker (CRLF) + */ + private static final String EOL_WINDOWS = "\r\n"; + + /** + * "Old Mac"-style end-of-line marker (CR) + */ + private static final String EOL_OLD_MAC = "\r"; + + /** + * Default end-of-line marker on current system + */ + private static final String EOL_SYSTEM_DEFAULT = System.getProperty("line.separator"); + + /** + * 清空文件 + */ + public static void truncate(File file) { + try (FileChannel outChan = new FileOutputStream(file, true).getChannel()) { + outChan.truncate(0); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * The support end-of-line marker modes + */ + public static enum Mode { + /** + * Unix-style end-of-line marker ("\n") + */ + LF, + + /** + * Windows-style end-of-line marker ("\r\n") + */ + CRLF, + + /** + * "Old Mac"-style end-of-line marker ("\r") + */ + CR + } + + /** + * The default end-of-line marker mode for the current system + */ + public static final Mode SYSTEM_DEFAULT = (EOL_SYSTEM_DEFAULT.equals(EOL_UNIX) ? Mode.LF : (EOL_SYSTEM_DEFAULT + .equals(EOL_WINDOWS) ? Mode.CRLF : (EOL_SYSTEM_DEFAULT.equals(EOL_OLD_MAC) ? Mode.CR : null))); + + static { + // Just in case... + if (SYSTEM_DEFAULT == null) { + throw new IllegalStateException("Could not determine system default end-of-line marker"); + } + } + + /** + * Determines the end-of-line {@link Mode} of a text file. + * + * @param textFile the file to investigate + * @return the end-of-line {@link Mode} of the given file, or {@code null} if it could not be determined + * @throws Exception + */ + public static Mode determineEOL(File textFile) + throws Exception { + if (!textFile.exists()) { + throw new IOException("Could not find file to open: " + textFile.getAbsolutePath()); + } + + FileInputStream fileIn = new FileInputStream(textFile); + BufferedInputStream bufferIn = new BufferedInputStream(fileIn); + try { + int prev = -1; + int ch; + while ((ch = bufferIn.read()) != -1) { + if (ch == '\n') { + if (prev == '\r') { + return Mode.CRLF; + } else { + return Mode.LF; + } + } else if (prev == '\r') { + return Mode.CR; + } + prev = ch; + } + throw new Exception("Could not determine end-of-line marker mode"); + } catch (IOException ioe) { + throw new Exception("Could not determine end-of-line marker mode", ioe); + } finally { + // Clean up: + IOUtils.closeQuietly(bufferIn); + } + } + + /** + * Checks whether the given text file has Windows-style (CRLF) line endings. + * + * @param textFile the file to investigate + * @return + * @throws Exception + */ + public static boolean hasWindowsEOL(File textFile) + throws Exception { + return Mode.CRLF.equals(determineEOL(textFile)); + } + + /** + * Checks whether the given text file has Unix-style (LF) line endings. + * + * @param textFile the file to investigate + * @return + * @throws Exception + */ + public static boolean hasUnixEOL(File textFile) + throws Exception { + return Mode.LF.equals(determineEOL(textFile)); + } + + /** + * Checks whether the given text file has "Old Mac"-style (CR) line endings. + * + * @param textFile the file to investigate + * @return + * @throws Exception + */ + public static boolean hasOldMacEOL(File textFile) + throws Exception { + return Mode.CR.equals(determineEOL(textFile)); + } + + /** + * Checks whether the given text file has line endings that conform to the system default mode (e.g. LF on Unix). + * + * @param textFile the file to investigate + * @return + * @throws Exception + */ + public static boolean hasSystemDefaultEOL(File textFile) + throws Exception { + return SYSTEM_DEFAULT.equals(determineEOL(textFile)); + } + + /** + * Convert the line endings in the given file to Unix-style (LF). + * + * @param textFile the file to process + * @throws IOException + */ + public static void convertToUnixEOL(File textFile) + throws IOException { + convertLineEndings(textFile, EOL_UNIX); + } + + /** + * Convert the line endings in the given file to Windows-style (CRLF). + * + * @param textFile the file to process + * @throws IOException + */ + public static void convertToWindowsEOL(File textFile) + throws IOException { + convertLineEndings(textFile, EOL_WINDOWS); + } + + /** + * Convert the line endings in the given file to "Old Mac"-style (CR). + * + * @param textFile the file to process + * @throws IOException + */ + public static void convertToOldMacEOL(File textFile) + throws IOException { + convertLineEndings(textFile, EOL_OLD_MAC); + } + + /** + * Convert the line endings in the given file to the system default mode. + * + * @param textFile the file to process + * @throws IOException + */ + public static void convertToSystemEOL(File textFile) + throws IOException { + convertLineEndings(textFile, EOL_SYSTEM_DEFAULT); + } + + /** + * Line endings conversion method. + * + * @param textFile the file to process + * @param eol the end-of-line marker to use (as a {@link String}) + * @throws IOException + */ + private static void convertLineEndings(File textFile, String eol) + throws IOException { + File temp = null; + BufferedReader bufferIn = null; + BufferedWriter bufferOut = null; + + try { + if (textFile.exists()) { + // Create a new temp file to write to + temp = new File(textFile.getAbsolutePath() + ".normalized"); + temp.createNewFile(); + + // Get a stream to read from the file un-normalized file + FileInputStream fileIn = new FileInputStream(textFile); + DataInputStream dataIn = new DataInputStream(fileIn); + bufferIn = new BufferedReader(new InputStreamReader(dataIn)); + + // Get a stream to write to the normalized file + FileOutputStream fileOut = new FileOutputStream(temp); + DataOutputStream dataOut = new DataOutputStream(fileOut); + bufferOut = new BufferedWriter(new OutputStreamWriter(dataOut)); + + int currentCharacter = bufferIn.read(); + + // Loop through each character read. + while (currentCharacter != -1) { + // Skip carriage returns. + if ((char)currentCharacter != '\r') { + bufferOut.write(currentCharacter); + } + currentCharacter = bufferIn.read(); + } + + // Close buffered reader & writer: + bufferIn.close(); + bufferOut.close(); + + // Remove the original file + textFile.delete(); + + // And rename the original file to the new one + temp.renameTo(textFile); + } else { + // If the file doesn't exist... + throw new IOException("Could not find file to open: " + textFile.getAbsolutePath()); + } + } finally { + // Clean up, temp should never exist + FileUtils.deleteQuietly(temp); + IOUtils.closeQuietly(bufferIn); + IOUtils.closeQuietly(bufferOut); + } + } + + /** + * 采用Files的delete方法,不直接使用file.delete是因为file.delete在删除失败的场景下只会返回false,没有具体的错误原因返回 + */ + public static String delete(File file) { + String path = file.getPath(); + try { + Files.delete(file.toPath()); + logger.info("删除文件成功,file: {}", path); + } catch (NoSuchFileException e) { + logger.info("删除文件失败,file: {},文件不存在", path); + } catch (IOException e) { + logger.warn("删除文件失败,file: {},e: {}", path, e.getMessage()); + } + return path; + } + +} diff --git a/common/src/main/java/com/imitate/common/util/GameHelper.java b/common/src/main/java/com/imitate/common/util/GameHelper.java new file mode 100644 index 0000000..e3ea67f --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/GameHelper.java @@ -0,0 +1,358 @@ +package com.imitate.common.util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.BuildResultCsts; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * 业务相关的帮助类 + * + * @author weishao + * @date 2017/11/05 + **/ +public final class GameHelper { + + private static final Logger logger = LoggerFactory.getLogger(GameHelper.class); + + /** + * 通过gitURL获取版本库名字 + * + * @param gitURL + * 版本库地址 + * @return 版本库名字 + */ + + public static String getGitRepoName(String gitURL) { + String repoName = gitURL.substring(gitURL.lastIndexOf("/") + 1, gitURL.lastIndexOf(".")); + return repoName; + } + + public static String removeGitUrlUser(String gitUrl) { + return gitUrl.substring(0, gitUrl.indexOf("//") + 2) + gitUrl.substring(gitUrl.indexOf("@") + 1); + } + + /** + * 通过gitUrl获取用户的identifier + * + * @param gitURL + * @return 用户identifier + */ + public static String getIdentifier(String gitURL) { + String tmp = gitURL.substring(0, gitURL.lastIndexOf("/")); + return tmp.substring(tmp.lastIndexOf("/") + 1); + } + + /** + * 提取脚本执行结果中平台封装的结果格式 + * + * @param src + * @return + */ + public static String getRealResult(String src) { + Pattern p = Pattern.compile(BuildResultCsts.FULL_RESULT_REG); + Matcher m = p.matcher(src); + if (m.find()) { + return m.group(0); + } else { + return null; + } + } + + public static String getTimeoutResUsage(String out) { + Pattern p = Pattern.compile(BuildResultCsts.TIMEOUT_RES_USAGE); + Matcher m = p.matcher(out); + if (m.find()) { + return m.group(0); + } + return null; + } + + /** + * 判段是否是合法的json字符串 + * + * @param src + * @return + */ + public static boolean isValidJSON(String src) { + try { + JSONObject.parseObject(src); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 判断是否为图片文件 + * + * @param pic + * @return + */ + public static boolean isPic(String pic) { + String regex = ".+(.JPEG|.jpeg|.JPG|.jpg|.GIF|.gif|.BMP|.bmp|.PNG|.png)$"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(pic); + + return m.find(); + } + + /** + * 判断是否为html + * + * @param html + * @return + */ + public static boolean isHtml(String html) { + String regex = ".+(.html|.htm)$"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(html); + return m.find(); + } + + /** + * 判断是否为apk + * + * @param apk + * @return + */ + public static boolean isApk(String apk) { + String regex = ".+(.apk)$"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(apk); + return m.find(); + } + + /** + * 判断是否为exe + * + * @param exe + * @return + */ + public static boolean isExe(String exe) { + String regex = ".+(.exe)$"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(exe); + return m.find(); + } + + /** + * 判断是否为txt + * + * @param txt + * @return + */ + public static boolean isTxt(String txt) { + String regex = ".+(.txt)$"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(txt); + return m.find(); + } + + /** + * 获取生成的apk的文件名字 + * + * @param dir + * @return + */ + public static String getApkName(String dir) { + String apkName = ""; + File file = new File(dir); + if (file.exists() && file.isDirectory()) { + File[] files = file.listFiles(); + for (File fileTmp : files) { + String fileName = fileTmp.getName(); + if (GameHelper.isApk(fileName)) { + return fileName; + } + } + } + return apkName; + } + + /** + * 清除无关的文件 + * + * @param dir + */ + public static void clearFiles(String dir) { + GameHelper.deleteFiles(dir, "pic"); + GameHelper.deleteFiles(dir, "apk"); + GameHelper.deleteFiles(dir, "html"); + GameHelper.deleteFiles(dir, "exe"); + GameHelper.deleteFiles(dir, "txt"); + } + + /** + * 删除文件夹下某种类型的文件 + * + * @param dir + * @param fileType + */ + public static void deleteFiles(String dir, String fileType) { + File file = new File(dir); + if (file.exists() && file.isDirectory()) { + File[] files = file.listFiles(); + for (File fileTmp : files) { + String fileName = fileTmp.getName(); + try { + if ("pic".equals(fileType) && GameHelper.isPic(fileName)) { + FileUtils.forceDelete(new File(fileTmp.getAbsolutePath())); + } + if ("apk".equals(fileType) && GameHelper.isApk(fileName)) { + FileUtils.forceDelete(new File(fileTmp.getAbsolutePath())); + } + if ("html".equals(fileType) && GameHelper.isHtml(fileName)) { + FileUtils.forceDelete(new File(fileTmp.getAbsolutePath())); + } + if ("exe".equals(fileType) && GameHelper.isExe(fileName)) { + FileUtils.forceDelete(new File(fileTmp.getAbsolutePath())); + } + if ("txt".equals(fileType) && GameHelper.isTxt(fileName)) { + FileUtils.forceDelete(new File(fileTmp.getAbsolutePath())); + } + } catch (Exception e) { + logger.error("删除实训生成文件{}失败", fileName, e); + } + } + } + } + + /** + * 取得评测执行超时情况下的平台封装结果(限定时间内执行脚本尚未获得结果,脚本还在运行) + */ + public static String getTooManyTimesResult(int caseSize, int maxTimes) { + String hint = "输出消息次数超限,最多输出%d次"; + hint = String.format(hint, maxTimes); + + return getResult(hint, caseSize, null, null); + } + + public static String getResult(String hint, int caseSize, String downloadStatus, String createPodStatus) { + hint = Base64Util.encode(hint); + + JSONObject result = new JSONObject(); + result.put("compileResult", hint); + JSONArray out = new JSONArray(caseSize); + for (int i = 0; i < caseSize; i++) { + out.add(hint); + } + result.put("out", out); + result.put(BuildResultCsts.DOWNLOAD_STATUS, downloadStatus); + result.put(BuildResultCsts.CREATE_POD_STATUS, createPodStatus); + return result.toJSONString(); + } + + /** + * 取得评测执行超时情况下的平台封装结果(限定时间内执行脚本尚未获得结果,脚本还在运行) + */ + public static String getTimeOutResult(int caseSize, String hint, List outputs) { + + JSONObject result = new JSONObject(); + result.put("compileResult", BuildResultCsts.COMPILE_SUCCESS_BASE64); + // outputs为空时,为用户user.out文件不存在,无法判定哪个用例超时,统一封装返回 + JSONArray out = new JSONArray(caseSize); + if (outputs == null) { + for (int i = 0; i < caseSize; i++) { + out.add(Base64Util.encode(hint)); + } + } else { + // 否则,取正常用例输出 + int i = 0; + for (; i < outputs.size(); i++) { + out.add(Base64Util.encode(outputs.get(i))); + } + // 超时用例提示 + if (i++ < caseSize) { + out.add(Base64Util.encode(hint)); + } + // 后继用例均为执行,封装提示 + String remainCaseHint = "测试集" + i + "代码评测超时,当前测试集未执行,请检查代码。"; + for (;i < caseSize; i++) { + out.add(Base64Util.encode(remainCaseHint)); + } + } + result.put("out", out); + + return result.toJSONString(); + } + + /** + * 当脚本错误时,返回平台封装的结果 + */ + public static String getShellErrorResult(int caseSize, String oriOut) { + JSONObject result = new JSONObject(); + String tip = BuildResultCsts.EVA_UNPUBLISHED_SCRIPT_ERROR_TIP; + if (StringUtils.isNotEmpty(oriOut)) { + if (StringUtils.isNotEmpty(oriOut) && oriOut.toLowerCase().contains(BuildResultCsts.INPUT_TOO_LONG_ERROR)) { + tip = BuildResultCsts.INPUT_TOO_LONG_TIP; + } + tip = tip + ",原始输出信息: " + oriOut; + } + result.put("compileResult", Base64Util.encode(tip)); + + JSONArray out = new JSONArray(caseSize); + for (int i = 0; i < caseSize; i++) { + out.add(Base64Util.encode("")); + } + result.put("out", out); + + return result.toJSONString(); + } + + /** + * 评测时候出现错误,返回提示 + */ + public static String getErrorResult(int caseSize, String tip) { + JSONObject result = new JSONObject(); + result.put("compileResult", tip); + JSONArray arr = new JSONArray(caseSize); + for (int i = 0; i < caseSize; i++) { + if (BuildResultCsts.EVA_OVER_MEMORY_LIMIT_BASE64.equals(tip)) { + arr.add(tip); + } else { + arr.add(Base64Util.encode("")); + } + } + result.put("out", arr); + + return result.toJSONString(); + } + + /** + * 调试模式下,封装调试结果 + */ + public static String getDebugOutPut(String result, String oriOut) { + JSONObject oriOutJson = JSONObject.parseObject(GameHelper.getRealResult(oriOut)); + + String compileResult = oriOutJson.getString("compileResult"); + oriOut = oriOut.replace(compileResult, Base64Util.decode(compileResult)); + + JSONArray outs = oriOutJson.getJSONArray("out"); + for (Object outObj : outs) { + String out = outObj.toString(); + String outDecode = Base64Util.decode(out); + if (outDecode.endsWith("\n")) { + outDecode = outDecode.substring(0, outDecode.length() - 1); + } + oriOut = oriOut.replace(out, outDecode); + } + + String resUsage = oriOutJson.getString("resUsage"); + if (StringUtils.isNotEmpty(resUsage)) { + oriOut = oriOut.replace(resUsage, Base64Util.decode(resUsage)); + } + + return "以下是本次评测结果:\n" + result + "\n当前模式下为您展示脚本原始输出,供调试使用,您脚本的原始输出为:\n" + oriOut; + } + +} diff --git a/common/src/main/java/com/imitate/common/util/HttpContextUtil.java b/common/src/main/java/com/imitate/common/util/HttpContextUtil.java new file mode 100644 index 0000000..7cc8c90 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/HttpContextUtil.java @@ -0,0 +1,73 @@ +package com.imitate.common.util; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * HttpContextUtils + * + * @author 悟空 + */ +public class HttpContextUtil { + + /** + * 获取query参数 + * @param request + * @return + */ + public static Map getParameterMapAll(HttpServletRequest request) { + Enumeration parameters = request.getParameterNames(); + Map params = new HashMap<>(); + while (parameters.hasMoreElements()) { + String parameter = parameters.nextElement(); + String value = request.getParameter(parameter); + params.put(parameter, value); + } + return params; + } + /** + * 获取请求Body + * + * @param request + * @return + */ + public static String getBodyString(ServletRequest request) { + StringBuilder sb = new StringBuilder(); + InputStream inputStream = null; + BufferedReader reader = null; + try { + inputStream = request.getInputStream(); + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return sb.toString(); + } +} diff --git a/common/src/main/java/com/imitate/common/util/HttpContextUtils.java b/common/src/main/java/com/imitate/common/util/HttpContextUtils.java new file mode 100644 index 0000000..f26cc78 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/HttpContextUtils.java @@ -0,0 +1,29 @@ +package com.imitate.common.util; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * http上下文 + * @author 悟空 + */ +public class HttpContextUtils { + + public static HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + + + public static String getDomain(){ + HttpServletRequest request = getHttpServletRequest(); + StringBuffer url = request.getRequestURL(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString(); + } + + public static String getOrigin(){ + HttpServletRequest request = getHttpServletRequest(); + return request.getHeader("Origin"); + } +} diff --git a/common/src/main/java/com/imitate/common/util/HttpHelper.java b/common/src/main/java/com/imitate/common/util/HttpHelper.java new file mode 100644 index 0000000..e90ddb9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/HttpHelper.java @@ -0,0 +1,327 @@ +package com.imitate.common.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; + +/** + * Created by guange on 30/10/2016. + */ +public final class HttpHelper { + private static final Logger log = LoggerFactory.getLogger(HttpHelper.class); + + /** + * 向指定URL发送GET方法的请求 + * + * @param url + * 发送请求的URL + * @param param + * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return URL 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) { + String result = ""; + BufferedReader in = null; + try { + String urlNameString = url; + if (StringUtils.isNotEmpty(param)) { + urlNameString += "?" + param; + } + URL realUrl = new URL(urlNameString); + // 打开和URL之间的连接 + URLConnection connection = realUrl.openConnection(); + // 设置通用的请求属性 + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + // 建立实际的连接 + connection.connect(); + // 获取所有响应头字段 + Map> map = connection.getHeaderFields(); + // 遍历所有的响应头字段 + for (String key : map.keySet()) { + if (log.isDebugEnabled()) { + log.debug((key + "--->" + map.get(key))); + } + } + // 定义 BufferedReader输入流来读取URL的响应 + in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + log.error("发送GET请求出现异常!", e); + } + // 使用finally块来关闭输入流 + finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception e2) { + // ignore quietly + e2.printStackTrace(); + } + } + return result; + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url + * 发送请求的 URL + * @param param + * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, Map param) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + long currentTimeMillis = System.currentTimeMillis(); + try { + URL realUrl = new URL(url); + // 打开和URL之间的连接 + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + // conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestMethod("POST"); + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + StringBuilder sb = new StringBuilder(); + for (Object key : param.keySet()) { + sb.append(key + "=" + param.get(key)); + sb.append("&"); + } + String data = sb.toString(); + data = data.substring(0, data.length() - 1); + log.debug("POST: " + url + " DATA: " + data); + OutputStream outputStream = conn.getOutputStream(); + outputStream.write(data.getBytes()); + outputStream.flush(); + outputStream.close(); + log.info("结果返回时间0{}", System.currentTimeMillis() - currentTimeMillis); + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + // System.out.println("发送 POST 请求出现异常!" + e); + // e.printStackTrace(); + log.error("发送 POST 请求出现异常!", e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore quietly + e.printStackTrace(); + } + } + log.info("结果返回时间1{},返回结果{}", System.currentTimeMillis() - currentTimeMillis, result); + return result; + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url + * 发送请求的 URL + * @param param + * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, Map param, int readTimeOut) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + long currentTimeMillis = System.currentTimeMillis(); + try { + URL realUrl = new URL(url); + // 打开和URL之间的连接 + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + // conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestMethod("POST"); + conn.setReadTimeout(readTimeOut); + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + StringBuilder sb = new StringBuilder(); + for (Object key : param.keySet()) { + sb.append(key + "=" + param.get(key)); + sb.append("&"); + } + String data = sb.toString(); + data = data.substring(0, data.length() - 1); + log.debug("POST: " + url + " DATA: " + data); + OutputStream outputStream = conn.getOutputStream(); + outputStream.write(data.getBytes()); + outputStream.flush(); + outputStream.close(); + log.info("结果返回时间0{}", System.currentTimeMillis() - currentTimeMillis); + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + // System.out.println("发送 POST 请求出现异常!" + e); + // e.printStackTrace(); + log.error("发送 POST 请求出现异常!", e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore quietly + e.printStackTrace(); + } + } + log.info("结果返回时间1{},返回结果{}", System.currentTimeMillis() - currentTimeMillis, result); + return result; + } + + /** + * 向指定 URL 发送POST请求 + * + * @param url + * 发送请求的 URL + * @param param + * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + try { + URL realUrl = new URL(url); + // 打开和URL之间的连接 + URLConnection conn = realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + // 获取URLConnection对象对应的输出流 + out = new PrintWriter(conn.getOutputStream()); + // 发送请求参数 + out.print(param); + // flush输出流的缓冲 + out.flush(); + // 定义BufferedReader输入流来读取URL的响应 + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + // System.out.println("发送 POST 请求出现异常!" + e); + // e.printStackTrace(); + log.error("发送 POST 请求出现异常!", e); + } + // 使用finally块来关闭输出流、输入流 + finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + // ignore quietly + ex.printStackTrace(); + } + } + return result; + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url + * 发送请求的 URL + * @param param + * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param, int readTimeOut) { + PrintWriter out = null; + BufferedReader in = null; + String result = ""; + long currentTimeMillis = System.currentTimeMillis(); + try { + URL realUrl = new URL(url); + // 打开和URL之间的连接 + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + // 设置通用的请求属性 + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestMethod("POST"); + conn.setReadTimeout(readTimeOut); + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + + log.debug("POST: " + url + " param: " + param); + OutputStream outputStream = conn.getOutputStream(); + outputStream.write(param.getBytes()); + outputStream.flush(); + outputStream.close(); + log.info("结果返回时间0{}", System.currentTimeMillis() - currentTimeMillis); + in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + } catch (Exception e) { + // System.out.println("发送 POST 请求出现异常!" + e); + // e.printStackTrace(); + log.error("发送 POST 请求出现异常!", e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore quietly + e.printStackTrace(); + } + } + log.info("结果返回时间1{},返回结果{}", System.currentTimeMillis() - currentTimeMillis, result); + return result; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/util/IpUtil.java b/common/src/main/java/com/imitate/common/util/IpUtil.java new file mode 100644 index 0000000..a03b8a7 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/IpUtil.java @@ -0,0 +1,50 @@ +package com.imitate.common.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; + +/** + * IP地址 + * @author 悟空 + */ +public class IpUtil { + private static Logger logger = LoggerFactory.getLogger(IpUtil.class); + + private static final String UNKNOWN = "unknown"; + + /** + * 获取IP地址 + * + * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 + * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 + */ + public static String getIpAddr(HttpServletRequest request) { + String ip = null; + try { + ip = request.getHeader("x-forwarded-for"); + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + } catch (Exception e) { + logger.error("IPUtils ERROR ", e); + } + + return ip; + } + +} diff --git a/common/src/main/java/com/imitate/common/util/IpUtils.java b/common/src/main/java/com/imitate/common/util/IpUtils.java new file mode 100644 index 0000000..466f577 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/IpUtils.java @@ -0,0 +1,75 @@ +package com.imitate.common.util; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +public final class IpUtils { + + public static List getLocalIpList() { + List ipList = new ArrayList(); + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + NetworkInterface networkInterface; + Enumeration inetAddresses; + InetAddress inetAddress; + String ip; + while (networkInterfaces.hasMoreElements()) { + networkInterface = networkInterfaces.nextElement(); + inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + inetAddress = inetAddresses.nextElement(); + if (inetAddress != null && inetAddress instanceof Inet4Address) { // IPV4 + ip = inetAddress.getHostAddress(); + ipList.add(ip); + } + } + } + } catch (SocketException e) { + throw new RuntimeException(e); + } + return ipList; + } + + public static String getLocalIpByName(String netNames) { + String[] arr = netNames.split(","); + for (String name : arr) { + try { + return IpUtils.getLocalIp(name); + } catch (Exception e) { + // ignore quietly + } + } + return null; + } + + public static String getLocalIp(String ifaceName) { + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + NetworkInterface networkInterface; + Enumeration inetAddresses; + InetAddress inetAddress; + String ip; + while (networkInterfaces.hasMoreElements()) { + networkInterface = networkInterfaces.nextElement(); + inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + inetAddress = inetAddresses.nextElement(); + if (inetAddress != null && inetAddress instanceof Inet4Address) { // IPV4 + ip = inetAddress.getHostAddress(); + if (ifaceName.equals(networkInterface.getName())) { + return ip; + } + } + } + } + } catch (SocketException e) { + throw new RuntimeException(e); + } + throw new RuntimeException("未找到网卡" + ifaceName); + } +} diff --git a/common/src/main/java/com/imitate/common/util/JedisUtil.java b/common/src/main/java/com/imitate/common/util/JedisUtil.java new file mode 100644 index 0000000..ef0e9de --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/JedisUtil.java @@ -0,0 +1,635 @@ +package com.imitate.common.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Response; +import redis.clients.jedis.Transaction; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by zzx on 17/10/24. + */ +public final class JedisUtil { + private static final Logger log = LoggerFactory.getLogger(JedisUtil.class); + + /** + * 获取符合模式的key + */ + public static Set keys(String pattern) { + Jedis jedis = null; + Set set = null; + try { + jedis = RedisPool.getJedis(); + set = jedis.keys(pattern); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return set; + } + + /** + * 获取zset中元素个数 + */ + public static int zlen(String key) { + Jedis jedis = null; + int len = 0; + try { + jedis = RedisPool.getJedis(); + len = jedis.zcard(key).intValue(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return len; + } + + /** + * 获取元素在zset中的排名 + */ + public static double zrank(String key, String value) { + Jedis jedis = null; + double rank = Double.MIN_VALUE; + try { + jedis = RedisPool.getJedis(); + if (jedis.zcard(key) == 0) { + return rank; + } + rank = jedis.zrank(key, value); + } catch (Exception e) { + rank = Double.MAX_VALUE; + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return rank; + } + + /** + * 将zset中队首元素弹出,若zset中没有元素,则返回null + */ + public static String zpop(String key) { + Jedis jedis = null; + String element = null; + try { + jedis = RedisPool.getJedis(); + if (jedis.zcard(key) != 0) { + element = jedis.zrange(key, 0, 0).iterator().next(); + jedis.zrem(key, element); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return element; + } + + /** + * 将一个新元素存到zset队尾,如果zset中尚没有元素,zadd(key, 1, value) + */ + public static void zpush(String key, String value) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + if (jedis.zcard(key) == 0) { + jedis.zadd(key, 1, value); + } else { + String lastElement = jedis.zrange(key, -1, -1).iterator().next(); + double lastScore = jedis.zscore(key, lastElement); + jedis.zadd(key, lastScore + 1, value); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * 将zset中指定元素score加1 + * + * 如果zset中不存在该元素,则此操作等同于zadd(key, 1, value) + */ + public static void zup(String key, String value) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.zincrby(key, 1, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * 将zset中指定元素score减1 + * + * 当分数小于1,代表这个元素代表的任务已完成,删除这个元素 + */ + public static void zdown(String key, String value) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.zincrby(key, -1, value); + if (jedis.zscore(key, value) < 1) { + jedis.zrem(key, value); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * 看一个key中某value元素是否存在,若不存在,返回false + */ + public static boolean zhas(String key, String value) { + Jedis jedis = null; + boolean has = true; + try { + jedis = RedisPool.getJedis(); + has = jedis.zscore(key, value) != null; + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return has; + } + + /** + * hash表,set一个键值对 + */ + public static void hset(String key, String field, String value) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.hset(key, field, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * set一个自动过期的键值对 + */ + public static void psetex(String key, String value, Long expireTime) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.psetex(key, expireTime, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * 设置key的值 + * @param key + * @param value + */ + public static void setnx(String key,String value){ + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.setnx(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + public static void lock(String key, String value, int second){ + Jedis jedis = RedisPool.getJedis(); + try { + doLock(key,value,second,jedis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + RedisPool.returnResource(jedis); + } + } + + private static void doLock(String key,String value, long second, Jedis jedis) throws InterruptedException { + if(jedis.get(key) == null) { + + String result = jedis.set(key, value, "NX", "EX", second); + + if ("OK".equals(result)) { + + } else { + Thread.sleep(50); + doLock(key, value, second, jedis); + } + }else{ + Thread.sleep(50); + doLock(key, value, second, jedis); + } + } + + public static boolean unlock(String key, String value){ + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + String result = jedis.get(key); + if(value.equals(result)){ + jedis.del(key); + return true; + } + return false; + } catch (Exception e) { + log.error(e.getMessage(), e); + return false; + } finally { + RedisPool.returnResource(jedis); + } + } + + + + /** + * 是否存在某个key + */ + public static boolean exists(String key) { + Jedis jedis = null; + Boolean exists = Boolean.FALSE; + try { + jedis = RedisPool.getJedis(); + exists = jedis.exists(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return exists; + } + + /** + * hash表,获取一个field的值 + */ + public static String hget(String key, String field) { + Jedis jedis = null; + String value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.hget(key, field); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + /** + * hash表,获取所有值 + */ + public static Map hgetAll(String key) { + Jedis jedis = null; + Map value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.hgetAll(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + /** + * hash表,删除一个field + */ + public static void hdel(String key, String field) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.hdel(key, field); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * list,查询list长度 + * @param key + * @return + */ + public static Long llen(String key) { + Jedis jedis = null; + Long value=0L; + try { + jedis = RedisPool.getJedis(); + value=jedis.llen(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + /** + * 获取list中 指定位置的值 + * index从0开始 + * 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil + * @param key + * @param index + * @return + */ + public static String lindex(String key,int index){ + Jedis jedis = RedisPool.getJedis(); + String result = jedis.lindex(key,index); + RedisPool.returnResource(jedis); + return result; + } + + /** + * 清空List数据 + * ltrim 让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 + * start和end为0时,即清空list + * @param key + * @return + */ + public static String clearList(String key){ + Jedis jedis = RedisPool.getJedis(); + String result = jedis.ltrim(key,0,0); + jedis.lpop(key); + RedisPool.returnResource(jedis); + return result; + } + + + + /** + * list,弹出并返回list的首元素 + * @param timeout 阻塞时间,blpop操作超过此时间未完成返回null + * @param key + * @return + */ + public static String blpop(int timeout,String key) { + Jedis jedis = null; + List value=null; + try { + jedis = RedisPool.getJedis(); + value=jedis.blpop(timeout,key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value==null? null:value.get(0); + } + + /** + * list,在list末尾添加元素 + * @param key + * @param value + * @return + */ + public static Long rpush(String key,String value) { + Jedis jedis = null; + Long success=0L; + try { + jedis = RedisPool.getJedis(); + success=jedis.rpush(key,value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return success; + } + + /** + * list,删除list中对应的元素 + * @param key + * @param count + * @param value + * @return + */ + public static Long lrem(String key,Long count,String value) { + Jedis jedis = null; + Long success=0L; + try { + jedis = RedisPool.getJedis(); + success=jedis.lrem(key,count,value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return success; + } + + /** + * set集合,向集合添加一个元素 + */ + public static void sadd(String key, String val) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.sadd(key, val); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * set集合,获取所有元素 + */ + public static Set smembers(String key) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + return jedis.smembers(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return new HashSet<>(2); + } + + + /** + * set集合,向集合删除一个元素 + */ + public static void srem(String key, String val) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.srem(key, val); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * String: set + */ + public static void set(String key, String value) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.set(key, value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * String: set 带过期时间 + */ + public static void set(String key, String value,long second) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.set(key,value,"nx", "ex", second); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * String: get + */ + public static String get(String key) { + Jedis jedis = null; + String value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.get(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + /** + * String: delete + */ + public static void del(String key) { + Jedis jedis = null; + try { + jedis = RedisPool.getJedis(); + jedis.del(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + } + + /** + * + * @param key + * @param increment 增量 + * @return + */ + public static Long incrBy(String key, long increment) { + Jedis jedis = null; + Long value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.incrBy(key, increment); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + + /** + * 更新key时间 + * @param key + * @param increment + * @return + */ + public static Long expire(String key, int increment) { + Jedis jedis = null; + Long value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.expire(key, increment); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + + /** + * 获取过期时间 如果获取的值为-1 说明此key 没有设置有效期 当值为 -2 时证明过了有效期。 + * @param key + * @return + */ + public static Long ttl(String key) { + Jedis jedis = null; + Long value = null; + try { + jedis = RedisPool.getJedis(); + value = jedis.ttl(key); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + RedisPool.returnResource(jedis); + } + return value; + } + + public static long incrByExpire(String key, long increment, int seconds) { + Jedis jedis = null; + Transaction multi = null; + Response value = null; + try { + jedis = RedisPool.getJedis(); + multi = jedis.multi(); + value = multi.incrBy(key, increment); + multi.expire(key, seconds); + multi.exec(); + } catch (Exception e) { + log.error(e.getMessage(), e); + if (multi != null) { + multi.discard(); + } + } finally { + RedisPool.returnResource(jedis); + } + return value == null ? 0 : value.get(); + } + +} + diff --git a/common/src/main/java/com/imitate/common/util/JsonUtils.java b/common/src/main/java/com/imitate/common/util/JsonUtils.java new file mode 100644 index 0000000..53dac29 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/JsonUtils.java @@ -0,0 +1,49 @@ +/******************************************************************************* +case_consumer * Copyright (c) 2005, 2014 springside.github.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + *******************************************************************************/ +package com.imitate.common.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +/** + * + * @author mumu + * + */ +public final class JsonUtils { + + private static ObjectMapper mapper = new ObjectMapper(); + + public static String toJson(Object object) { + + try { + return mapper.writeValueAsString(object); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T toBean(String content, Class clazz) { + + try { + return mapper.readValue(content, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T toBean(String content, TypeReference valueTypeRef) { + + try { + return mapper.readValue(content, valueTypeRef); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/common/src/main/java/com/imitate/common/util/MD5Util.java b/common/src/main/java/com/imitate/common/util/MD5Util.java new file mode 100644 index 0000000..4201897 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/MD5Util.java @@ -0,0 +1,51 @@ +package com.imitate.common.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.MessageDigest; + +/** + * MD5 工具 + */ +public final class MD5Util { + private static final Logger log = LoggerFactory.getLogger(MD5Util.class); + + /** + * md5 加密 + * + * @param input + * @return + */ + public static String toMd5(String input) { + try { + MessageDigest md5 = MessageDigest.getInstance("md5"); + md5.update(input.getBytes()); + return byteToString(md5.digest()); + } catch (Exception e) { + log.error("计算container信息md5出错", e); + + } + return null; + } + + /** + * 字节转字符串 + * + * @param byteValue + * @return + */ + private static String byteToString(byte[] byteValue) { + StringBuilder sb = new StringBuilder(); + int len = byteValue.length; + for (int i = 0; i < len; i++) { + byte b = byteValue[i]; + String hex = Integer.toHexString(0xFF & b); + if (hex.length() == 1) { + sb.append("0"); + } + sb.append(hex); + } + return sb.subSequence(8, 24).toString(); + } +} diff --git a/common/src/main/java/com/imitate/common/util/OKHttp3Utils3.java b/common/src/main/java/com/imitate/common/util/OKHttp3Utils3.java new file mode 100644 index 0000000..bfce032 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/OKHttp3Utils3.java @@ -0,0 +1,313 @@ +package com.imitate.common.util; + + +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.core.type.TypeReference; +import com.imitate.common.constant.ApiResultCsts; +import com.imitate.common.bean.ApiResult; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * okhttp3 客户端 + * @author 悟空 + */ +public class OKHttp3Utils3 { + + private static final Logger log = LoggerFactory.getLogger(OKHttp3Utils3.class); + + public static int DEFAULT_TIME_OUT = 10; + + /** + * 全局实例可以保持http1.1 连接复用,线程池复用, 减少tcp的网络连接,关闭, + * 如果每次一个请求,在高并发下,thread增多到1W,close_wait持续增加到6k。 + */ + private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder() + .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES)) + .connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).build(); + + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + + + /** + * 不同timeout的连接池 + */ + public static ConcurrentHashMap cacheClients = new ConcurrentHashMap(); + + + public static OkHttpClient getHttpClient(int timeout) { + + if (timeout == 0 || DEFAULT_TIME_OUT == timeout) { + return OK_HTTP_CLIENT; + } else { + OkHttpClient okHttpClient = cacheClients.get(timeout); + if (okHttpClient == null) { + return syncCreateClient(timeout); + } + return okHttpClient; + } + } + + private static synchronized OkHttpClient syncCreateClient(int timeout) { + OkHttpClient okHttpClient; + + okHttpClient = cacheClients.get(timeout); + if (okHttpClient != null) { + return okHttpClient; + } + + okHttpClient = new OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS).readTimeout(timeout, TimeUnit.SECONDS).writeTimeout(timeout, TimeUnit.SECONDS).build(); + cacheClients.put(timeout, okHttpClient); + return okHttpClient; + + } + + + /** + * GET请求 + * + * @param url + * @return Optional + */ + public static String get(String url, int timeout){ + try { + Request request = new Request.Builder().url(url) + .build(); + return getHttpClient(timeout).newCall(request).execute().body().string(); + }catch (Exception e){ + } + return null; + } + + + /** + * 带认证的get请求 + */ + public static String get(String url, int timeout,String authorization) { + try { + Request request = new Request.Builder().header("Authorization",authorization).url(url) + .build(); + return getUnsafeOkHttpClient().newCall(request).execute().body().string(); + }catch (Exception e){ + + } + return null; + } + + /** + * 带认证的delete请求 + */ + public static String delete(String url,Map param, int timeout, String authorization){ + try { + FormBody.Builder builder = new FormBody.Builder(); + for(String key : param.keySet()){ + builder.add(key, (String) param.get(key)); + } + RequestBody body = builder.build(); + Request request = new Request.Builder().header("Authorization",authorization).url(url).delete(body) + .build(); + return getUnsafeOkHttpClient().newCall(request).execute().body().string(); + }catch (Exception e){ + + } + return null; + } + + /** + * 带认证的表单类post请求 + */ + public static String sendPost(String url, Map param,String authorization) { + long start = System.currentTimeMillis(); + try { + FormBody.Builder builder = new FormBody.Builder(); + for(String key : param.keySet()){ + builder.add(key, (String) param.get(key)); + } + RequestBody body = builder.build(); + Request okRequest = new Request.Builder() + .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .addHeader("Authorization",authorization) + .url(url).post(body).build(); + return getUnsafeOkHttpClient().newCall(okRequest).execute().body().string(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start)); + } + return null; + } + + + + + /** + * post form表单提交 + * @param url + * @param param + * @return + */ + public static ApiResult sendPost(String url, Map param) { + long start = System.currentTimeMillis(); + try { + FormBody.Builder builder = new FormBody.Builder(); + for(String key : param.keySet()){ + builder.add(key, (String) param.get(key)); + } + RequestBody body = builder.build(); + Request okRequest = new Request.Builder().header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").url(url).post(body).build(); + return getApiResult(getHttpClient(0).newCall(okRequest).execute().body().string()); + } catch (Exception e) { + } finally { + log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start)); + } + return null; + } + + + public static String requestPost(String url, Map param) { + long start = System.currentTimeMillis(); + try { + FormBody.Builder builder = new FormBody.Builder(); + for(String key : param.keySet()){ + builder.add(key, (String) param.get(key)); + } + RequestBody body = builder.build(); + Request okRequest = new Request.Builder().header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").url(url).post(body).build(); + String s = getUnsafeOkHttpClient().newCall(okRequest).execute().body().string(); + log.info("请求自动登陆接口返回值:{}",s); + return s; + } catch (Exception e) { + e.printStackTrace(); + } finally { + log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start)); + } + return null; + } + + + + + + + /** + * POST请求,参数为json格式。 + * + * @param url + * @param jsonObject + * @return Optional + */ + public static void sendPost(String url, JSONObject jsonObject) { + long start = System.currentTimeMillis(); + try { + RequestBody body = RequestBody.create(JSON, jsonObject.toJSONString()); + Request request = new Request.Builder().header("Content-Type","application/json").url(url).post(body).build(); + Call call = getHttpClient(15000).newCall(request); + call.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + //异步请求失败之后的回调 + log.error("异步发送请求失败,url:{},json:{}",url,jsonObject.toJSONString()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + //异步请求成功之后的回调 + log.info("异步发送请求成功,url:{},json:{}",url,jsonObject.toJSONString()); + response.close(); + } + }); + } catch (Exception e) { + throw e; + } finally { + log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start)); + } + } + + + /** + * post json提交 + * @param url + * @param jsonStr + * @return + */ + public static ApiResult sendPost(String url, String jsonStr) { + long start = System.currentTimeMillis(); + try { + RequestBody body = RequestBody.create(JSON, jsonStr); + Request okRequest = new Request.Builder().header("Content-Type","application/json").url(url).post(body).build(); + return getApiResult(getHttpClient(0).newCall(okRequest).execute().body().string()); + } catch (Exception e) { + } finally { + log.info("request url {} ,total time {} ms", url, (System.currentTimeMillis() - start)); + } + return null; + } + + + private static ApiResult getApiResult(String resultStr) { + ApiResult result = new ApiResult<>(); + try { + log.debug("同步发送 POST 请求,返回结果: {}", resultStr); + result = JsonUtils.toBean(resultStr, new TypeReference>() {}); + }catch (Exception e){ + result.setCode(ApiResultCsts.CODE_FAIL); + log.error("同步发送 POST 请求,返回结果异常: {}", e); + } + return result; + } + + + + + /** + * okHttp3添加信任所有证书 + * @return + */ + public static OkHttpClient getUnsafeOkHttpClient() { + try { + final TrustManager[] trustAllCerts =new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public java.security.cert.X509Certificate[]getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + } + }; + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + OkHttpClient.Builder builder =new OkHttpClient.Builder(); + builder.sslSocketFactory(sslSocketFactory,(X509TrustManager)trustAllCerts[0]); + builder.hostnameVerifier(new HostnameVerifier() { + // 可以将不需要忽略的域名放入数组,也可为空(忽略所有证书) + String[] arr =new String[]{}; + @Override + public boolean verify(String hostname, SSLSession session) { + return !Arrays.asList(arr).contains(hostname); + } + }); + return builder.build(); + }catch (Exception e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/common/src/main/java/com/imitate/common/util/OjEvaResultUtils.java b/common/src/main/java/com/imitate/common/util/OjEvaResultUtils.java new file mode 100644 index 0000000..a1b259b --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/OjEvaResultUtils.java @@ -0,0 +1,24 @@ +package com.imitate.common.util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +/** + * oj评测结果处理工具类 + */ +public class OjEvaResultUtils { + + public static String getOjResultByStatus(String status, int caseSize, String tip) { + JSONObject result = new JSONObject(); + String tipEncode = Base64Util.encode(tip); + result.put("compileResult", tipEncode); + JSONArray out = new JSONArray(caseSize); + for (int i = 0; i < caseSize; i++) { + out.add(tipEncode); + } + result.put("out", out); + result.put("status", status); + + return result.toJSONString(); + } +} diff --git a/common/src/main/java/com/imitate/common/util/ProcessStatusUtil.java b/common/src/main/java/com/imitate/common/util/ProcessStatusUtil.java new file mode 100644 index 0000000..35e37bc --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/ProcessStatusUtil.java @@ -0,0 +1,21 @@ +package com.imitate.common.util; + +/** + * + * @author mm + * + */ +public final class ProcessStatusUtil { + + /** + * 执行shell命令并获取输出 + */ + public static boolean isTimeout(int exitStatus) { + return exitStatus == 124 || exitStatus == (128 + 9); + } + + public static boolean isError(int exitStatus) { + return exitStatus == -1; + } + +} diff --git a/common/src/main/java/com/imitate/common/util/R.java b/common/src/main/java/com/imitate/common/util/R.java new file mode 100644 index 0000000..51931c1 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/R.java @@ -0,0 +1,81 @@ +package com.imitate.common.util; + +import com.alibaba.fastjson.JSONArray; +import com.imitate.common.enums.ErrorCodeEnum; +import org.apache.http.HttpStatus; + +import java.util.HashMap; +import java.util.Map; + +/** + * 前端统一返回数据 + * @author yanchao + */ +public class R extends HashMap { + + + @Override + public Object get(Object key) { + return this.get(key); + } + + public R(){ + put("result","success"); + put("errorCode", ErrorCodeEnum.SUCCESS.getValue()); + put("errorMsg",""); + put("data",new JSONArray()); + } + + public static R error(){ + return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,"未知异常,请联系管理员"); + } + public static R error(String msg){ + return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,msg); + } + public static R error(int code, String msg){ + R r = new R(); + r.put("errorCode",code); + r.put("errorMsg",msg); + return r; + } + public static R error(String code, String msg){ + R r = new R(); + r.put("result","fail"); + r.put("errorCode",code); + r.put("errorMsg",msg); + return r; + } + public static R ok(String msg){ + R r = new R(); + r.put("errorMsg",msg); + return r; + } + public static R ok(Map map){ + R r = new R(); + r.putAll(map); + return r; + } + + public static R ok(){ + return new R(); + } + + + @Override + public R put(String key, Object value){ + super.put(key,value); + return this; + } + + public R setData(Object data) { + if(data == null || data == ""){ + put("data",new JSONArray()); + }else{ + JSONArray rst = new JSONArray(); + rst.add(data); + put("data",rst); + } + return this; + } + +} diff --git a/common/src/main/java/com/imitate/common/util/RedisPool.java b/common/src/main/java/com/imitate/common/util/RedisPool.java new file mode 100644 index 0000000..86eec5f --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/RedisPool.java @@ -0,0 +1,74 @@ +package com.imitate.common.util; + +import com.imitate.common.bean.BeanFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.exceptions.JedisException; + +public final class RedisPool { + private static final Logger log = LoggerFactory.getLogger(RedisPool.class); + + // 可用连接实例的最大数目,默认为8; + // 如果赋值为-1,则表示不限制,如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽) + private static Integer MAX_TOTAL = 1024; + // 控制一个pool最多有多少个状态为idle(空闲)的jedis实例,默认值是8 + private static Integer MAX_IDLE = 200; + // 等待可用连接的最大时间,单位是毫秒,默认值为-1,表示永不超时。 + // 如果超过等待时间,则直接抛出JedisConnectionException + private static Integer MAX_WAIT_MILLIS = 10000; + private static Integer TIMEOUT = 10000; + // 在borrow(用)一个jedis实例时,是否提前进行validate(验证)操作; + // 如果为true,则得到的jedis实例均是可用的 + private static Boolean TEST_ON_BORROW = true; + private static JedisPool jedisPool = null; + + private synchronized static void initJedisPool() { + if (jedisPool == null) { + try { + Environment env = BeanFactory.getObejct(Environment.class); + String ADDR = env.getProperty("spring.redis.host"); + int PORT = Integer.parseInt(env.getProperty("spring.redis.port")); + String AUTH = env.getProperty("spring.redis.password"); + + JedisPoolConfig config = new JedisPoolConfig(); + config.setMaxTotal(MAX_TOTAL); + config.setMaxIdle(MAX_IDLE); + config.setMaxWaitMillis(MAX_WAIT_MILLIS); + config.setTestOnBorrow(TEST_ON_BORROW); + log.info("开始reids初始化..."); + jedisPool = "".equals(AUTH) ? new JedisPool(config, ADDR, PORT, TIMEOUT) + : new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH); + log.info("测试jedisPool 信息:{}", jedisPool.getResource().toString()); + } catch (Exception e) { + throw new RuntimeException("jedisPool初始化错误", e); + } + } + } + + /** + * 获取Jedis实例 + * + * @return + */ + public static Jedis getJedis() { + if (jedisPool == null) { + initJedisPool(); + } + return jedisPool.getResource(); + } + + public static void returnResource(final Jedis jedis) { + if (jedis != null) { + // jedis.close()取代jedisPool.returnResource(jedis)方法将3.0版本开始 + try { + jedis.close(); + } catch (JedisException e) { + log.error(e.getMessage(), e); + } + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/util/ShellExeCallBack.java b/common/src/main/java/com/imitate/common/util/ShellExeCallBack.java new file mode 100644 index 0000000..7fd3611 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/ShellExeCallBack.java @@ -0,0 +1,12 @@ +package com.imitate.common.util; + +public interface ShellExeCallBack { + + /** + * + * @param line + * @param param + * @return 此 CallBack 特殊信息, + */ + boolean processLine(String line, T param); +} diff --git a/common/src/main/java/com/imitate/common/util/ShellUtil.java b/common/src/main/java/com/imitate/common/util/ShellUtil.java new file mode 100644 index 0000000..2b7afa9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/ShellUtil.java @@ -0,0 +1,179 @@ +package com.imitate.common.util; + +import com.imitate.common.bean.ShellResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * @author guange + * @date 26/02/2017 + */ +public final class ShellUtil { + + private static final Logger logger = LoggerFactory.getLogger(ShellUtil.class); + + /** + * 执行shell命令并获取输出 + */ + public static String execute(String command) { + return executeAndGetExitStatus(command).getOut(); + } + + /** + * 执行shell命令并获得输出及退出码,失败重试 共尝试retryTimes次 + */ + public static ShellResult executeAndGetExitStatus(String command, int retryTimes) { + ShellResult result = new ShellResult(); + + for (int i = 0; i < retryTimes; i++) { + result = executeAndGetExitStatus(command); + if (result.getExitStatus() != 0) { + logger.info("执行shell错误, 再次执行 command: {}, result: {}, times: {}", command, result, i); + } else { + break; + } + } + + return result; + } + + /** + * 执行命令并获得输出以及退出码 + */ + public static ShellResult executeAndGetExitStatus(String command) { + ShellResult result = new ShellResult(); + + StringBuilder out = new StringBuilder(); + Integer exitStatus = -1; + + ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command); + pb.redirectErrorStream(true); + try { + Process process = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + out.append(line); + out.append(System.getProperty("line.separator")); + } + exitStatus = process.waitFor(); + + } catch (Exception e) { + logger.error("执行shell出错, command:{}", command, e); + } + + result.setOut(out.toString().trim()); + result.setExitStatus(exitStatus); + logger.debug("execute shell command: {}, out: {}, status: {}", command, out, exitStatus); + + return result; + } + + /** + * 执行命令并获得输出以及退出码 + */ + public static ShellResult executeAndGetExitStatus(String command, ShellExeCallBack callBack, T param) { + ShellResult result = new ShellResult(); + + StringBuilder out = new StringBuilder(); + Integer exitStatus = -1; + + ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command); + pb.redirectErrorStream(true); + try { + Process process = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + // 分布输出信息固定为一行,若需要处理多行信息从历史commit"评测分布输出多行处理版本提交"找回 + boolean processed = callBack.processLine(line, param); + if (processed) { + continue; + } + + logger.debug("executeAndGetExitStatus line: {}", line); // TODO 此处稳定后去除 + out.append(line); + out.append(System.getProperty("line.separator")); + } + exitStatus = process.waitFor(); + + } catch (Exception e) { + logger.error("执行shell出错, command:{}", command, e); + } + + result.setOut(out.toString().trim()); + result.setExitStatus(exitStatus); + + return result; + } + + + /** + * 执行shell命令 + * @param cmd + * @param callBack + * @param param + * @param + * @return + */ + public static ShellResult executeAndGetErrorStatus(String cmd,ShellExeCallBack callBack, T param) { + ShellResult result = new ShellResult(); + StringBuffer sb = new StringBuffer(); + Integer exitStatus = -1; + try { + Process ps = Runtime.getRuntime().exec(cmd); + + //获取执行正确结果 + BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + // 分布输出信息固定为一行,若需要处理多行信息从历史commit"评测分布输出多行处理版本提交"找回 + boolean processed = callBack.processLine(line, param); + if (processed) { + continue; + } + + sb.append(line).append(System.getProperty("line.separator")); + } + exitStatus = ps.waitFor(); + }catch (Exception e) { + logger.error("执行shell出错, command:{}", cmd, e); + } + result.setOut(sb.toString().trim()); + result.setExitStatus(exitStatus); + return result; + } + + /** + * 执行shell命令 + * @param cmd + * @param + * @return + */ + public static ShellResult executeAndGetErrorStatus(String cmd) { + ShellResult result = new ShellResult(); + StringBuffer sb = new StringBuffer(); + Integer exitStatus = -1; + try { + Process ps = Runtime.getRuntime().exec(cmd); + + //获取执行正确结果 + BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + sb.append(line).append(System.getProperty("line.separator")); + } + exitStatus = ps.waitFor(); + }catch (Exception e) { + logger.error("执行shell出错, command:{}", cmd, e); + } + result.setOut(sb.toString().trim()); + result.setExitStatus(exitStatus); + return result; + } + + +} diff --git a/common/src/main/java/com/imitate/common/util/SignUtil.java b/common/src/main/java/com/imitate/common/util/SignUtil.java new file mode 100644 index 0000000..867c102 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/SignUtil.java @@ -0,0 +1,140 @@ +package com.imitate.common.util; + + +import lombok.extern.slf4j.Slf4j; + +import java.security.MessageDigest; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +/** + * 签名算法工具类 + * @author yanchao + */ +@Slf4j +public class SignUtil { + private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; + + private final static String randomBaseStr = "abcdefghijklmnopqrstuvwxyz0123456789"; + // 自定义,签名私钥的key ,web端提供 + private static String SIGN_KEY = "sk"; + // 自定义,签名私钥的value,需web 端提供 + private static String SIGN_VALUE = "9NMU8ushmFu8SN1EKHOhvo9jmv1qp0"; + private static String CHARSET_NAME = "UTF-8"; + //签名key + private static final String SIGN = "sign"; + + public static String signMap(Map map) { + StringBuilder param = new StringBuilder(); + Iterator> entries = map.entrySet().iterator(); + //TreeMap自动按字母排序 + Map params = new TreeMap<>(); + while (entries.hasNext()) { + Map.Entry entry = entries.next(); + String key = entry.getKey(); + String[] values = entry.getValue(); + if (values == null) { + entries.remove(); + } + if (SIGN.equals(key)) { + continue; + } + params.put(key, values); + } + + Iterator> entries2 = params.entrySet().iterator(); + int i = 0; + while (entries2.hasNext()) { + Map.Entry entry = entries2.next(); + String key = entry.getKey(); + String[] values = entry.getValue(); + if (values.length == 1) { + if(i == 0){ + param.append(key).append("=").append(values[0]); + }else{ + param.append("&").append(key).append("=").append(values[0]); + } + } + i++; + } + //添加私钥 + param.append("&").append(SIGN_KEY).append("=").append(SIGN_VALUE); + log.debug("最终签名认证字符串:{}",param.toString()); + return MD5Encode(param.toString(), CHARSET_NAME); + } + + + public static String signMap(String path, Map map) { + StringBuilder param = new StringBuilder(); + Iterator> entries = map.entrySet().iterator(); + //TreeMap自动按字母排序 + Map params = new TreeMap<>(); + while (entries.hasNext()) { + Map.Entry entry = entries.next(); + String key = entry.getKey(); + String values = String.valueOf(entry.getValue()); + if (values == null) { + entries.remove(); + } + if (SIGN.equals(key)) { + continue; + } + params.put(key, values); + } + + Iterator> entries2 = params.entrySet().iterator(); + int i = 0; + while (entries2.hasNext()) { + Map.Entry entry = entries2.next(); + String key = entry.getKey(); + String values = entry.getValue(); + if (values != null) { + if(i == 0){ + param.append(key).append("=").append(values); + }else{ + param.append("&").append(key).append("=").append(values); + } + } + i++; + } + //添加私钥 + param.append("&").append(SIGN_KEY).append("=").append(SIGN_VALUE); + log.debug("最终签名认证字符串:{}",param.toString()); + return MD5Encode(param.toString(), CHARSET_NAME); + } + + private static String byteArrayToHexString(byte b[]) { + StringBuffer resultSb = new StringBuffer(); + for (int i = 0; i < b.length; i++) + resultSb.append(byteToHexString(b[i])); + + return resultSb.toString(); + } + + private static String byteToHexString(byte b) { + int n = b; + if (n < 0) { + n += 256; + } + int d1 = n / 16; + int d2 = n % 16; + return hexDigits[d1] + hexDigits[d2]; + } + + public static String MD5Encode(String origin, String charsetname) { + String resultString = null; + try { + resultString = new String(origin); + MessageDigest md = MessageDigest.getInstance("MD5"); + if (charsetname == null || "".equals(charsetname)) { + resultString = byteArrayToHexString(md.digest(resultString.getBytes())); + } else { + resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); + } + } catch (Exception exception) { + } + assert resultString != null; + return resultString.toUpperCase(); + } +} diff --git a/common/src/main/java/com/imitate/common/util/StringUtil.java b/common/src/main/java/com/imitate/common/util/StringUtil.java new file mode 100644 index 0000000..d0e8aef --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/StringUtil.java @@ -0,0 +1,17 @@ +package com.imitate.common.util; + +/** + * 字符串处理工具类 + * + * @author 威少 + */ +public class StringUtil { + /** + * 替换\r\n -> \n,\r -> \n + * @param source 源串 + * @return 目标串 + */ + public static String replaceLineSeparator(String source) { + return source.replace("\r\n", "\n").replace("\r", "\n"); + } +} diff --git a/common/src/main/java/com/imitate/common/util/ThreadUtils.java b/common/src/main/java/com/imitate/common/util/ThreadUtils.java new file mode 100644 index 0000000..912c8ed --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/ThreadUtils.java @@ -0,0 +1,18 @@ +package com.imitate.common.util; + +/** + * + * @author mm + * + */ +public final class ThreadUtils { + + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (Exception e) { + // ingore quitely + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/util/TimeCostUtils.java b/common/src/main/java/com/imitate/common/util/TimeCostUtils.java new file mode 100644 index 0000000..9a47849 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/TimeCostUtils.java @@ -0,0 +1,62 @@ +package com.imitate.common.util; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.k8s.bean.TimeCost; +import org.apache.commons.lang3.StringUtils; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * 评测时间记录工具类 + */ +public class TimeCostUtils { + + public static String getStepTimeCost(Instant startInstant) { + return formatTimeCost(getStepTimeCostDouble(startInstant)); + } + + public static double getStepTimeCostDouble(Instant startInstant) { + return (double) Duration.between(startInstant, Instant.now()).toMillis() / 1000; + } + + public static String formatTimeCost(double costTime) { + return String.format("%.3f", costTime); + } + + public static TimeCost getTimeCost(JSONObject buildParams, Instant encapsulateStartInstant) { + TimeCost timeCost = new TimeCost(); + LocalDateTime evaluateStartTime = LocalDateTime.parse(buildParams.getString("evaluateStartTime")); + timeCost.setEvaluateStartTime(evaluateStartTime); + timeCost.setCreatePod(buildParams.getString("createPodCost")); + + String pullCost = buildParams.getString("pullCost"); + pullCost = pullCost == null ? "0" : pullCost; + timeCost.setPull(pullCost); + + // 封装结果完毕,将封装评测结果的耗时计入评测耗时中 + double encapsulateCost = (double) Duration.between(encapsulateStartInstant, Instant.now()).toMillis() / 1000; + double executeCost = 0.0; + if (StringUtils.isNotBlank(buildParams.getString("execute"))) { + executeCost = Double.parseDouble(buildParams.getString("execute")); + } + // 各阶段时间消耗将在此方法中传到前台,没必要再存储到Jedis + timeCost.setExecute(String.format("%.3f", executeCost + encapsulateCost)); + // 评测结束(回传开始) + timeCost.setEvaluateEndTime(LocalDateTime.now()); + // 评测总时间(除回传结果给educoder之外的总时间) 当前时间 - 存储在redis中的开始时间 + + ZoneId zone = ZoneId.systemDefault(); + double evaluateAllTime = (double) Duration.between(evaluateStartTime.atZone(zone).toInstant(), Instant.now()).toMillis() / 1000; + timeCost.setEvaluateAllTime(String.format("%.3f", evaluateAllTime)); + return timeCost; + } + + public static int getRemainingTime(String startTime, int timeLimit) { + LocalDateTime evaluateStartTime = LocalDateTime.parse(startTime); + ZoneId zone = ZoneId.systemDefault(); + return timeLimit + 10 - (int) (Duration.between(evaluateStartTime.atZone(zone).toInstant(), Instant.now()).toMillis() / 1000); + } +} diff --git a/common/src/main/java/com/imitate/common/util/TimeHelper.java b/common/src/main/java/com/imitate/common/util/TimeHelper.java new file mode 100644 index 0000000..51e6def --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/TimeHelper.java @@ -0,0 +1,44 @@ +package com.imitate.common.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +/** + * 业务相关的时间帮助类 + * @author weishao + * @date 2018/8/29 + */ +public final class TimeHelper { + /** + * 获取以毫秒表示的时间的格式化时间 + */ + public static String getTime(long millis) { + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format( + Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault()).toLocalDateTime()); + } + + /** + * 获取当前时间 + */ + public static String getCurrentTime() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.now()); + } + + /** + * 获取当前时间 + */ + public static String getCurrentTime(String pattern) { + return DateTimeFormatter.ofPattern(pattern).format(LocalDateTime.now()); + } + + /** + * 将表示时间的字符串转化为时间戳 + */ + public static long convertTimeToMillis(String time) { + return LocalDateTime.parse(time).toInstant(ZoneOffset.of("+8")).toEpochMilli(); + } + +} diff --git a/common/src/main/java/com/imitate/common/util/TpUtils.java b/common/src/main/java/com/imitate/common/util/TpUtils.java new file mode 100644 index 0000000..08338cd --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/TpUtils.java @@ -0,0 +1,153 @@ +package com.imitate.common.util; + + +import com.imitate.common.constant.TpCsts; + +/** + * + * @author mm + * + */ +public final class TpUtils { + + private TpUtils() { + } + + /** + * 转化评测类 podType (0.evaluate,1.webssh,2.evassh, 3.vnc, 4.jupyter) + * + * @param podType + * @return + */ + public static String cvtEvaPodType(int podType) { + switch (podType) { + case TpCsts.POD_TYPE_EVALUATE: + return TpCsts.TYPE_EVALUATE; + case TpCsts.POD_TYPE_WEBSSH: + return TpCsts.TYPE_WEBSSH; + case TpCsts.POD_TYPE_EVASSH: + return TpCsts.TYPE_EVASSH; + case TpCsts.POD_TYPE_VNC: + return TpCsts.TYPE_VNC; + case TpCsts.POD_TYPE_JUPYTER: + return TpCsts.TYPE_JUPYTER; + case TpCsts.POD_TYPE_VSCODE: + return TpCsts.TYPE_VSCODE; + case TpCsts.POD_TYPE_WEB: + return TpCsts.TYPE_WEB; + default: + return TpCsts.TYPE_EVALUATE; + } + } + + /** + * 转化评测类 podType (0.evaluate,1.webssh,2.evassh, 3.vnc, 4.jupyter) + * + * @param podType + * @return + */ + public static int cvtEvaPodType(String podType) { + switch (podType) { + case TpCsts.TYPE_EVALUATE: + return TpCsts.POD_TYPE_EVALUATE; + case TpCsts.TYPE_WEBSSH: + return TpCsts.POD_TYPE_WEBSSH; + case TpCsts.TYPE_EVASSH: + return TpCsts.POD_TYPE_EVASSH; + case TpCsts.TYPE_VNC: + return TpCsts.POD_TYPE_VNC; + case TpCsts.TYPE_JUPYTER: + return TpCsts.POD_TYPE_JUPYTER; + case TpCsts.TYPE_VSCODE: + return TpCsts.POD_TYPE_VSCODE; + case TpCsts.TYPE_WEB: + return TpCsts.POD_TYPE_WEB; + default: + return TpCsts.POD_TYPE_EVALUATE; + } + } + + /** + * 构造评测类 podName + * + * @param tpiID + * @param podType + * @return + */ + public static String buildEvaPodName(String tpiID, Integer podType) { + String type = cvtEvaPodType(podType); + return type + "-" + tpiID; + } + + /** + * 构造webssh podName + * + * @return webssh pod名 + */ + public static String buildWebsshPodName(String tpiID, Integer podType) { + String baseName = podType != TpCsts.POD_TYPE_EVASSH ? TpCsts.TYPE_WEBSSH : TpCsts.TYPE_EVASSH; + return baseName + "-" + tpiID; + } + + /** + * 构造vscode podName + * + * @return vscode pod名 + */ + public static String buildVscodePodName(String tpiID, Integer podType) { + String baseName = podType == TpCsts.POD_TYPE_VSCODE ? "vscode": TpCsts.TYPE_WEB; + return baseName + "-" + tpiID; + } + + + /** + * 构造web服务 podName + * @param tpiID + * @param podType + * @return + */ + public static String buildWebPodName(String tpiID,Integer podType){ + String baseName = TpUtils.cvtEvaPodType(podType); + return baseName + "-" + tpiID; + } + + + /** + * 构造service名称 + * @param tpiID tpiID + * @param podType pod类型 + * @param port 端口 + * @return serviceName + */ + public static String buildEvaServiceName(String tpiID, Integer podType, Integer port) { + return buildEvaPodName(tpiID, podType) + "-" + port; + } + + public static String getTpiID(String podName) { + int index = podName.indexOf("-"); + return podName.substring(index + 1); + } + + public static boolean isStatefulPod(Integer podType) { + if (podType == TpCsts.POD_TYPE_VNC || podType == TpCsts.POD_TYPE_WEBSSH + || podType == TpCsts.POD_TYPE_EVASSH) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + public static String buildRemoteClusterGitURL(String tpiGitURL, String localGitServiceDomain) { + tpiGitURL = tpiGitURL.replace("//", ""); + return localGitServiceDomain + tpiGitURL.substring(tpiGitURL.indexOf("/"), tpiGitURL.length()); + } + + public static String buildWindowsGitUrl(String gitUrl, String gitServiceDomain) { + gitUrl = buildRemoteClusterGitURL(gitUrl, gitServiceDomain); + return gitUrl.split("//")[0] + "//windowsgit:xinedugit@" + gitUrl.split("//")[1]; + } + + + public static boolean isCreateImageName(String imageName) { + return imageName.startsWith("edu-"); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/util/compile/CompileUtils.java b/common/src/main/java/com/imitate/common/util/compile/CompileUtils.java new file mode 100644 index 0000000..f86c0f6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/compile/CompileUtils.java @@ -0,0 +1,39 @@ +/******************************************************************************* +case_consumer * Copyright (c) 2005, 2014 springside.github.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + *******************************************************************************/ +package com.imitate.common.util.compile; + +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; +import java.io.ByteArrayOutputStream; + +/** + * + * @author mumu + * + */ +public final class CompileUtils { + public static final int CODE_SUCCESS = 0; + + public static CompileResult compileJavaFile(String codeFilePath) { + CompileResult cr = new CompileResult(); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + ByteArrayOutputStream errOut = new ByteArrayOutputStream(); + int code = compiler.run(null, null, errOut, codeFilePath); + cr.code = code; + + if (code != CODE_SUCCESS) { + cr.msg = errOut.toString(); + } + + return cr; + } + + public static class CompileResult { + public int code; + public String msg; + } +} diff --git a/common/src/main/java/com/imitate/common/util/id/IdUtils.java b/common/src/main/java/com/imitate/common/util/id/IdUtils.java new file mode 100644 index 0000000..e99a718 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/id/IdUtils.java @@ -0,0 +1,20 @@ +package com.imitate.common.util.id; + +/** + * + * @author mm + * + */ +public final class IdUtils { + + private static SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); + + private IdUtils() { + + } + + public static long nextId() { + return idWorker.nextId(); + } + +} diff --git a/common/src/main/java/com/imitate/common/util/id/SnowflakeIdWorker.java b/common/src/main/java/com/imitate/common/util/id/SnowflakeIdWorker.java new file mode 100644 index 0000000..d96c7c9 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/id/SnowflakeIdWorker.java @@ -0,0 +1,147 @@ +package com.imitate.common.util.id; + +/** + * Twitter_Snowflake
+ * SnowFlake的结构如下(每部分用-分开):
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - + * 000000000000
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T + * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
+ * 加起来刚好64位,为一个Long型。
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + */ +public class SnowflakeIdWorker { + + // ==============================Fields=========================================== + /** 开始时间截 (2015-01-01) */ + private final long twepoch = 1420041600000L; + + /** 机器id所占的位数 */ + private final long workerIdBits = 5L; + + /** 数据标识id所占的位数 */ + private final long datacenterIdBits = 5L; + + /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** 支持的最大数据标识id,结果是31 */ + private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** 序列在id中占的位数 */ + private final long sequenceBits = 12L; + + /** 机器ID向左移12位 */ + private final long workerIdShift = sequenceBits; + + /** 数据标识id向左移17位(12+5) */ + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** 时间截向左移22位(5+5+12) */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** 工作机器ID(0~31) */ + private long workerId; + + /** 数据中心ID(0~31) */ + private long datacenterId; + + /** 毫秒内序列(0~4095) */ + private long sequence = 0L; + + /** 上次生成ID的时间截 */ + private long lastTimestamp = -1L; + + // ==============================Constructors===================================== + /** + * 构造函数 + * + * @param workerId + * 工作ID (0~31) + * @param datacenterId + * 数据中心ID (0~31) + */ + public SnowflakeIdWorker(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException( + String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException( + String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + public synchronized long nextId() { + long timestamp = timeGen(); + + // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException(String.format( + "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + // 如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + // 毫秒内序列溢出 + if (sequence == 0) { + // 阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + // 时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + // 上次生成ID的时间截 + lastTimestamp = timestamp; + + // 移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) // + | (datacenterId << datacenterIdShift) // + | (workerId << workerIdShift) // + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp + * 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/imitate/common/util/net/NetUtils.java b/common/src/main/java/com/imitate/common/util/net/NetUtils.java new file mode 100644 index 0000000..fbc440a --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/net/NetUtils.java @@ -0,0 +1,64 @@ +package com.imitate.common.util.net; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +public final class NetUtils { + + public static List getNetDevices() { + List devList = new ArrayList<>(); + try { + Enumeration netInterfaces = (Enumeration) NetworkInterface + .getNetworkInterfaces(); + while (netInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = (NetworkInterface) netInterfaces.nextElement(); + + Enumeration ias = networkInterface.getInetAddresses(); + while (ias.hasMoreElements()) { + InetAddress inetAddress = (InetAddress) ias.nextElement(); + if (inetAddress != null && !inetAddress.isLoopbackAddress() // 非127.0.0.1 + && inetAddress.getHostAddress().matches("(\\d{1,3}\\.){3}\\d{1,3}")) { + BridgeNetDevice dev = new BridgeNetDevice(); + dev.ip = inetAddress.getHostAddress(); + dev.mac = getMacFromBytes(networkInterface.getHardwareAddress()); + devList.add(dev); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return devList; + } + + private static String getMacFromBytes(byte[] bytes) { + StringBuffer mac = new StringBuffer(); + byte currentByte; + boolean first = false; + for (byte b : bytes) { + if (first) { + mac.append("-"); + } + currentByte = (byte) ((b & 240) >> 4); + mac.append(Integer.toHexString(currentByte)); + currentByte = (byte) (b & 15); + mac.append(Integer.toHexString(currentByte)); + first = true; + } + return mac.toString().toUpperCase(); + } + + public static class BridgeNetDevice { + public String ip; + public String mac; + + @Override + public String toString() { + return "BridgeNetDevice [ip=" + ip + ", mac=" + mac + "]"; + } + + } +} diff --git a/common/src/main/java/com/imitate/common/util/net/SocketUtils.java b/common/src/main/java/com/imitate/common/util/net/SocketUtils.java new file mode 100644 index 0000000..195b21a --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/net/SocketUtils.java @@ -0,0 +1,25 @@ +package com.imitate.common.util.net; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +public final class SocketUtils { + public static void connect(String host, int port) { + int seconds = 10; + Socket socket = new Socket(); + try { + socket.connect(new InetSocketAddress(host, seconds * 1000)); + } catch (IOException e) { + String msg = String.format("can not connect to host [%s] port [%d] in [%d] seconds", host, port, seconds); + throw new RuntimeException(msg, e); + } finally { + try { + socket.close(); + } catch (IOException e) { + // ignore quietly + } + } + + } +} diff --git a/common/src/main/java/com/imitate/common/util/net/TelnetUtils.java b/common/src/main/java/com/imitate/common/util/net/TelnetUtils.java new file mode 100644 index 0000000..d390fe6 --- /dev/null +++ b/common/src/main/java/com/imitate/common/util/net/TelnetUtils.java @@ -0,0 +1,29 @@ +package com.imitate.common.util.net; + +import org.apache.commons.net.telnet.TelnetClient; + +import java.io.IOException; + +public final class TelnetUtils { + public static void connect(String hostname, int port, int timeout) { + + TelnetClient telnetClient = new TelnetClient("vt200"); // 指明Telnet终端类型,否则会返回来的数据中文会乱码 + telnetClient.setDefaultTimeout(timeout * 1000); + + try { + telnetClient.connect(hostname, port); + + } catch (IOException e) { + String msg = String.format("can not telnet to host [%s] port [%d] in [%d] seconds", hostname, port, + timeout); + throw new RuntimeException(msg, e); + } finally { + try { + telnetClient.disconnect(); + } catch (IOException e) { + // ignore quietly + } + } + + } +} diff --git a/common/src/main/resources/META-INF/spring.factories b/common/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..4afc177 --- /dev/null +++ b/common/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +#Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.imitate.common.test.MyStarterAutoConfiguration \ No newline at end of file diff --git a/common/src/main/resources/common.properties b/common/src/main/resources/common.properties new file mode 100644 index 0000000..578d8f8 --- /dev/null +++ b/common/src/main/resources/common.properties @@ -0,0 +1,142 @@ +mystarter.config.enable=true +mystarter.config.student.name=yc +mystarter.config.student.age=18 +mystarter.config.student.gender=man + + + + +useMq=true +netNames=en0 +#web\u56de\u8c03\u5730\u5740 +educoderURL=https://www.educoder.net/api/myshixuns/training_task_status.json +simpileEducoderURL=https://www.educoder.net/api/myshixuns/code_runinng_message.json +ojEvaCallBackURL=https://www.educoder.net/api/myproblems/:tpiid/listen_result.json + +#git\u670d\u52a1\u5668\u57df\u540d\uff0c\u8fdc\u7a0b\u96c6\u7fa4\u9700\u8981\u4f7f\u7528\u8fd9\u4e2a\u57df\u540d\u6765\u4e0b\u8f7d +gitServiceDomain=https://git.educoder.net + +#\u6d4b\u8bd5\u7248dockerURL(\u4e3b\u8282\u70b9) +dockerMaster=http://172.16.94.148:4243 +#\u6d4b\u8bd5\u7248dockerURL(\u526f\u8282\u70b9) +dockerDeputies=http://172.16.94.167:4243;http://172.16.94.169:4243;http://172.16.94.172:4243 + +#\u955c\u50cf\u4ed3\u5e93\u5730\u5740 +registry=BRI_DOCKER_REGISTRY + + +#GIT_IP +gitIP=172.16.94.154 +gitBackUpIP=172.16.94.154 +gitPort=30122 +gitBackupDirs=/opt/repositories/2018,/opt/repositories/2018-2 +gitRepoDir=/opt/repositories +#git\u7528\u6237\u540d\u53ca\u5bc6\u7801 +gitUsername=edugit +gitPassword=xinedugit# + +workspace=/opt/workspace +secretWorkspace=/opt/workspace/secret +datadir=/opt/shixunfiles +bigDataDir=/opt/bigfiles + + + +#GPU \u955c\u50cf +gpuImage=nvidia +gpuBigImage=gpu-big +#shenlong \u955c\u50cf\u7279\u6027 +shenlong.image.character=shenlong + +#novnc port +vncPort=6901 + +#jupyter \u76f8\u5173\u914d\u7f6e +jupyterPort=80 +jupyterSurvivalMinute=60 +jupyterDelayMinute=20 +tpmJupyterDelayMinute=120 +tpmJupyterSurvivalMinute=120 + +webssh.delayMinute=21 + +eva.MaxPoolSize=800 +eva.HttpMaxConnTotal=800 +eva.HttpMaxConnPerRoute=200 +git.HttpMaxConnTotal=400 +git.HttpMaxConnPerRoute=400 +k8s.MaxConcurrentRequests=640 +k8s.MaxConcurrentRequestsPerHost=50 + +windows.forwardTableId=ftb-2zepf6ofbt3qitk1jevs0 +windows.externalIp=39.105.62.120 +windows.username=administrator +windows.password=Edu_123123 +windows.workspace=C:/Windows/AppReadiness/ +windows.initTime=3600 +windows.delayTime=3600 +linux.username=root + + +testCaseDir=/opt/bigfiles/filecase +testCaseActualOutDir=/opt/workspace/filecase + + + + +#limit of running pod number +maxRunningPodNum=20000 +#\u6d4b\u8bd5\u7248\u8d44\u6e90\u603b\u91cf +cpuAllocatable=4000 +memAllocatable=10000 + + +# defaultTimeLimit of executing time in a pod. +defaultTimeLimit=90 +# delay delete time +evaluate.pod.pass.delayDeleteTime=0 +evaluate.pod.default.delayDeleteTime=60 +#\u7f16\u8bd1\u7ed3\u679c\u6700\u5927\u957f\u5ea6 +outputSize=65535 + + + + +# sophgo +sophgo.apiKey=touge@126.com +sophgo.apiSecret=y2odnlm44zsbmd0j1aznzzqrev5w76cj +# sophgo API address +sophgo.api.domain=http://175.102.182.50:19028 +# sophgo sub ???????? url ???? +sophgo.api.notifyUrl=http://pre-bridge.educoder.net/bridge/sophgo/notify +sophgo.delayTime=3600 + +# nfs? +workspace.nfs.configs=/opt/workspace,/opt/workspace2 +bridge.errorOutPuts=\u7edf\u7e41\u5fd9\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\n\ +\u7a0b\u5e8f\u6267\u884c\u5931\u8d25\u5bfc\u81f4\u8bc4\u6d4b\u63d0\u524d\u7ec8\u6b62\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u6216\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\n\ +\u6b21\u8bc4\u6d4b\u7f51\u7edc\u5ef6\u8fdf\u8f83\u9ad8\uff0c\u8d44\u6e90\u65e0\u6cd5\u6b63\u5e38\u52a0\u8f7d\uff0c\u8bf7\u0031\u0030\u0073\u540e\u91cd\u8bd5\n\ +\u5f53\u524d\u5b9e\u9a8c\u73af\u5883\u6b63\u5728\u66f4\u65b0\u4e2d\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u6216\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\uff01\n\ +\u5f53\u524d\u7f51\u7edc\u8f83\u5dee\uff0c\u4ee3\u7801\u4e0b\u8f7d\u8d85\u65f6\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\uff01\n\ +\u8bc4\u6d4b\u811a\u672c\u8bbe\u7f6e\u5f02\u5e38\uff0c\u5efa\u8bae\u60a8\u5728\u5b9e\u8bad\u7684\u914d\u7f6e\u9875\u9762\u91cd\u65b0\u9009\u62e9\u6216\u4fee\u6539\u8bc4\u6d4b\u811a\u672c + +# ??????? +dingding.bridge.evaluation.token=1069c45d4dfab187bb95599faeaf3687b630f79eda0d6fff914a18ee9e25db6f +dingding.bridge.evaluation.secret=SEC7431097248ee9c19b9d7a2c61883e29082bea79c88c3e8f02e5fa121c2837dbc + +# ?????client?? +aliyun.flex.accessKeyId=LTAI5tG6z2pg3yQ42RQZLUKc +aliyun.flex.accessKeySecret=EzNHWle4HDZERwnc5syBPV86PZLmow +# ????id +aliyun.flex.ruleAri=ari:acs:ess:cn-hangzhou:1829848226361863:scalingrule/asr-bp1cozzmradkyct0cntj + +#\u8ba4\u8bc1\u5bc6\u94a5 +secretKey=priEn3UwXfJs3PmyXnSG + +#vscode?????? ???? codeserver1.vnc.educoder.net +vscodeDomain=testcodeserver1.vnc.educoder.net + + +spring.config.import=classpath:cron.properties + + diff --git a/common/src/main/resources/cron.properties b/common/src/main/resources/cron.properties new file mode 100644 index 0000000..3b8b008 --- /dev/null +++ b/common/src/main/resources/cron.properties @@ -0,0 +1,48 @@ +# \u5237\u65b0\u96c6\u7fa4\u914d\u7f6e\u4fe1\u606f +ClusterConfigTask=*/5 * * * * ? + +# \u5237\u65b0\u96c6\u7fa4\u8282\u70b9\u4fe1\u606f +ClusterTask=*/5 * * * * ? + +# \u5237\u65b0platform_config +RefreshPlatformConfigTask=0 */1 * * * ? + +# \u5237\u65b0\u8d44\u6e90\u4f7f\u7528\u7387 +RefreshNodeResUsageTask=*/5 * * * * ? + +# \u5237\u65b0sys_config +RefreshSysConfigTask=*/5 * * * * ? + +# \u5237\u65b0SecurityContextConfig +RefreshSecurityContextConfigTask=*/5 * * * * ? + +# \u5220\u9664oj\u8bc4\u6d4b\u6570\u7edf\u8ba1 +DelOjEvaStatTask=0 0 3 * * ? + +# \u6e05\u7406oj pod +DelOjPodTask=0 40 5 * * ? + +# \u6e05\u7406\u8d85\u65f6\u9057\u6f0fpod +DelMissPodTask=0 10 3 * * ? + +# \u8bc4\u6d4b\u6bcf\u65e5\u7edf\u8ba1\u4efb\u52a1 +EvaDayStatTask=0 20 3 * * ? + +DelHisCreateImageTask=0 30 3 * * ? + +DelHisK8sNodeImageTask=0 40 3 * * ? + +RefreshNodeStatus=*/10 * * * * ? + +RefreshNodePreReduce=*/10 * * * * ? + +RefreshNodeErrorCheck=*/30 * * * * ? + +ForwardEntryCheckTask=*/10 * * * * ? + +ForwardEntryDelete=0 20 3 * * ? + +ReplayLogSaveToDb=0 0/3 * * * ? + + +SophgoSshTask=0 0/25 * * * ? \ No newline at end of file diff --git a/common/src/main/resources/mybatis/BridgePodMapper.xml b/common/src/main/resources/mybatis/BridgePodMapper.xml new file mode 100644 index 0000000..b4a85f0 --- /dev/null +++ b/common/src/main/resources/mybatis/BridgePodMapper.xml @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, name, tpi_ID, build_ID, uid, k8s_create_time, delete_time, + sec_key, + request_time, + pull, create_pod, + execute, evaluate_all_time, + node_name, + node_ip, download_status, create_pod_status, + compile_status, + run_status, status, image_name, image_version, + cpu_limit, memory_limit, cpu_request, memory_request + + + + + insert into bridge_pod (id, name, tpi_ID, build_ID, uid, + k8s_create_time, delete_time, sec_key, + request_time, pull, create_pod, + execute, evaluate_all_time, node_name, + node_ip, download_status, + create_pod_status, + compile_status, run_status, status, + image_version, + image_name, + cpu_limit, + memory_limit, + cpu_request, + memory_request + ) + values + (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, + #{tpiID,jdbcType=VARCHAR}, + #{buildID,jdbcType=VARCHAR}, + #{uid,jdbcType=VARCHAR}, + #{k8sCreateTime,jdbcType=TIMESTAMP}, + #{deleteTime,jdbcType=TIMESTAMP}, + #{secKey,jdbcType=VARCHAR}, + #{requestTime,jdbcType=TIMESTAMP}, + #{pull,jdbcType=DOUBLE}, + #{createPod,jdbcType=DOUBLE}, + #{execute,jdbcType=DOUBLE}, + #{evaluateAllTime,jdbcType=DOUBLE}, + #{nodeName,jdbcType=VARCHAR}, + #{nodeIp,jdbcType=VARCHAR}, + #{downloadStatus,jdbcType=CHAR}, + #{createPodStatus,jdbcType=CHAR}, + #{compileStatus,jdbcType=CHAR}, + #{runStatus,jdbcType=CHAR}, + #{status,jdbcType=CHAR}, + #{imageName,jdbcType=VARCHAR}, + #{imageVersion,jdbcType=VARCHAR}, + #{cpuLimit,jdbcType=DOUBLE}, + #{memoryLimit,jdbcType=INTEGER}, + #{cpuRequest,jdbcType=DOUBLE}, + #{memoryRequest,jdbcType=INTEGER} + ) + + + insert into bridge_pod + + + id, + + + name, + + + tpi_ID, + + + build_ID, + + + uid, + + + k8s_create_time, + + + delete_time, + + + sec_key, + + + request_time, + + + pull, + + + create_pod, + + + execute, + + + evaluate_all_time, + + + node_name, + + + node_ip, + + + download_status, + + + create_pod_status, + + + compile_status, + + + run_status, + + + status, + + + image_name, + + + image_version, + + + cpu_limit, + + + memory_limit, + + + cpu_request, + + + memory_request, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{tpiID,jdbcType=VARCHAR}, + + + #{buildID,jdbcType=VARCHAR}, + + + #{uid,jdbcType=VARCHAR}, + + + #{k8sCreateTime,jdbcType=TIMESTAMP}, + + + #{deleteTime,jdbcType=TIMESTAMP}, + + + #{secKey,jdbcType=VARCHAR}, + + + #{requestTime,jdbcType=TIMESTAMP}, + + + #{pull,jdbcType=DOUBLE}, + + + #{createPod,jdbcType=DOUBLE}, + + + #{execute,jdbcType=DOUBLE}, + + + #{evaluateAllTime,jdbcType=DOUBLE}, + + + #{nodeName,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{downloadStatus,jdbcType=CHAR}, + + + #{createPodStatus,jdbcType=CHAR}, + + + #{compileStatus,jdbcType=CHAR}, + + + #{runStatus,jdbcType=CHAR}, + + + #{status,jdbcType=CHAR}, + + + #{imageName,jdbcType=VARCHAR}, + + + #{imageVersion,jdbcType=VARCHAR}, + + + #{cpuLimit,jdbcType=DOUBLE}, + + + #{memoryLimit,jdbcType=INTEGER}, + + + #{cpuRequest,jdbcType=DOUBLE}, + + + #{memoryRequest,jdbcType=INTEGER}, + + + + + update bridge_pod + + + name = #{name,jdbcType=VARCHAR}, + + + tpi_ID = #{tpiID,jdbcType=VARCHAR}, + + + build_ID = #{buildID,jdbcType=VARCHAR}, + + + uid = #{uid,jdbcType=VARCHAR}, + + + k8s_create_time = #{k8sCreateTime,jdbcType=TIMESTAMP}, + + + delete_time = #{deleteTime,jdbcType=TIMESTAMP}, + + + sec_key = #{secKey,jdbcType=VARCHAR}, + + + request_time = #{requestTime,jdbcType=TIMESTAMP}, + + + pull = #{pull,jdbcType=DOUBLE}, + + + create_pod = #{createPod,jdbcType=DOUBLE}, + + + execute = #{execute,jdbcType=DOUBLE}, + + + evaluate_all_time = #{evaluateAllTime,jdbcType=DOUBLE}, + + + node_name = #{nodeName,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + download_status = #{downloadStatus,jdbcType=CHAR}, + + + create_pod_status = #{createPodStatus,jdbcType=CHAR}, + + + compile_status = #{compileStatus,jdbcType=CHAR}, + + + run_status = #{runStatus,jdbcType=CHAR}, + + + status = #{status,jdbcType=CHAR}, + + + image_name = #{imageName,jdbcType=VARCHAR}, + + + image_version = #{imageVersion,jdbcType=VARCHAR}, + + + cpu_limit = #{cpuLimit,jdbcType=DOUBLE}, + + + memory_limit = #{memoryLimit,jdbcType=INTEGER}, + + + cpu_request = #{cpuRequest,jdbcType=DOUBLE}, + + + memory_request = #{memoryRequest,jdbcType=INTEGER}, + + + where id = #{id,jdbcType=BIGINT} + + + update bridge_pod + set name = #{name,jdbcType=VARCHAR}, + tpi_ID = #{tpiID,jdbcType=VARCHAR}, + build_ID = #{buildID,jdbcType=VARCHAR}, + uid = #{uid,jdbcType=VARCHAR}, + k8s_create_time = + #{k8sCreateTime,jdbcType=TIMESTAMP}, + delete_time = + #{deleteTime,jdbcType=TIMESTAMP}, + sec_key = #{secKey,jdbcType=VARCHAR}, + request_time = #{requestTime,jdbcType=TIMESTAMP}, + pull = + #{pull,jdbcType=DOUBLE}, + create_pod = #{createPod,jdbcType=DOUBLE}, + execute = #{execute,jdbcType=DOUBLE}, + evaluate_all_time = + #{evaluateAllTime,jdbcType=DOUBLE}, + node_name = + #{nodeName,jdbcType=VARCHAR}, + node_ip = #{nodeIp,jdbcType=VARCHAR}, + download_status = #{downloadStatus,jdbcType=CHAR}, + create_pod_status = + #{createPodStatus,jdbcType=CHAR}, + compile_status = + #{compileStatus,jdbcType=CHAR}, + run_status = + #{runStatus,jdbcType=CHAR}, + status = #{status,jdbcType=CHAR}, + image_name = + #{imageName,jdbcType=VARCHAR}, + image_version = + #{imageVersion,jdbcType=VARCHAR}, + cpu_limit = + #{cpuLimit,jdbcType=DOUBLE}, + memory_limit = + #{memoryLimit,jdbcType=INTEGER}, + cpu_request = + #{cpuRequest,jdbcType=DOUBLE}, + memory_request = + #{memoryRequest,jdbcType=INTEGER} + where id = + #{id,jdbcType=BIGINT} + + + + + update bridge_pod + set delete_time = #{deleteTime,jdbcType=TIMESTAMP} + where name = #{name,jdbcType=VARCHAR} and delete_time is null + + and request_time >= #{requestTime,jdbcType=TIMESTAMP} + + + + + delete from + bridge_pod + where request_time < #{requestTime,jdbcType=TIMESTAMP} + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/ClusterConfigMapper.xml b/common/src/main/resources/mybatis/ClusterConfigMapper.xml new file mode 100644 index 0000000..392ba14 --- /dev/null +++ b/common/src/main/resources/mybatis/ClusterConfigMapper.xml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, name, local, main_switch, weight, auto_scale, master_url, ca_cert_data, client_cer_data, client_key_data, username, password, + kubeconfig, local_web_socket, local_ssh_nodes, local_show_server, local_vnc_show_server, + local_git_url, remote_web_socket, remote_ssh_nodes, remote_show_server, remote_vnc_show_server, + remote_git_url, white_images, black_images, create_time, update_time + + + + + insert into cluster_config (id, name, local, main_switch, + weight, auto_scale, master_url, ca_cert_data, client_cer_data, client_key_data, + username, password, kubeconfig, + local_web_socket, local_ssh_nodes, local_show_server, + local_vnc_show_server, local_git_url, remote_web_socket, + remote_ssh_nodes, remote_show_server, remote_vnc_show_server, + remote_git_url, white_images, black_images, + create_time, update_time) + values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{local,jdbcType=BIT}, #{mainSwitch,jdbcType=VARCHAR}, + #{weight,jdbcType=DOUBLE}, #{autoScale,jdbcType=BIT}, #{masterUrl,jdbcType=VARCHAR}, + #{caCertData,jdbcType=VARCHAR}, #{clientCerData,jdbcType=VARCHAR}, #{clientKeyData,jdbcType=VARCHAR}, + #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{kubeconfig,jdbcType=VARCHAR}, + #{localWebSocket,jdbcType=VARCHAR}, #{localSshNodes,jdbcType=VARCHAR}, #{localShowServer,jdbcType=VARCHAR}, + #{localVncShowServer,jdbcType=VARCHAR}, #{localGitUrl,jdbcType=VARCHAR}, #{remoteWebSocket,jdbcType=VARCHAR}, + #{remoteSshNodes,jdbcType=VARCHAR}, #{remoteShowServer,jdbcType=VARCHAR}, #{remoteVncShowServer,jdbcType=VARCHAR}, + #{remoteGitUrl,jdbcType=VARCHAR}, #{whiteImages,jdbcType=VARCHAR}, #{blackImages,jdbcType=VARCHAR}, + #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) + + + insert into cluster_config + + + id, + + + name, + + + local, + + + main_switch, + + + weight, + + + auto_scale, + + + master_url, + + + ca_cert_data, + + + client_cer_data, + + + client_key_data, + + + username, + + + password, + + + kubeconfig, + + + local_web_socket, + + + local_ssh_nodes, + + + local_show_server, + + + local_vnc_show_server, + + + local_git_url, + + + remote_web_socket, + + + remote_ssh_nodes, + + + remote_show_server, + + + remote_vnc_show_server, + + + remote_git_url, + + + white_images, + + + black_images, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{local,jdbcType=BIT}, + + + #{mainSwitch,jdbcType=VARCHAR}, + + + #{weight,jdbcType=DOUBLE}, + + + #{autoScale,jdbcType=BIT}, + + + #{masterUrl,jdbcType=VARCHAR}, + + + #{caCertData,jdbcType=VARCHAR}, + + + #{clientCerData,jdbcType=VARCHAR}, + + + #{clientKeyData,jdbcType=VARCHAR}, + + + #{username,jdbcType=VARCHAR}, + + + #{password,jdbcType=VARCHAR}, + + + #{kubeconfig,jdbcType=VARCHAR}, + + + #{localWebSocket,jdbcType=VARCHAR}, + + + #{localSshNodes,jdbcType=VARCHAR}, + + + #{localShowServer,jdbcType=VARCHAR}, + + + #{localVncShowServer,jdbcType=VARCHAR}, + + + #{localGitUrl,jdbcType=VARCHAR}, + + + #{remoteWebSocket,jdbcType=VARCHAR}, + + + #{remoteSshNodes,jdbcType=VARCHAR}, + + + #{remoteShowServer,jdbcType=VARCHAR}, + + + #{remoteVncShowServer,jdbcType=VARCHAR}, + + + #{remoteGitUrl,jdbcType=VARCHAR}, + + + #{whiteImages,jdbcType=VARCHAR}, + + + #{blackImages,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update cluster_config + + + name = #{name,jdbcType=VARCHAR}, + + + local = #{local,jdbcType=BIT}, + + + main_switch = #{mainSwitch,jdbcType=VARCHAR}, + + + weight = #{weight,jdbcType=DOUBLE}, + + + auto_scale = #{autoScale,jdbcType=BIT}, + + + master_url = #{masterUrl,jdbcType=VARCHAR}, + + + ca_cert_data = #{caCertData,jdbcType=VARCHAR}, + + + client_cer_data = #{clientCerData,jdbcType=VARCHAR}, + + + client_key_data = #{clientKeyData,jdbcType=VARCHAR}, + + + username = #{username,jdbcType=VARCHAR}, + + + password = #{password,jdbcType=VARCHAR}, + + + kubeconfig = #{kubeconfig,jdbcType=VARCHAR}, + + + local_web_socket = #{localWebSocket,jdbcType=VARCHAR}, + + + local_ssh_nodes = #{localSshNodes,jdbcType=VARCHAR}, + + + local_show_server = #{localShowServer,jdbcType=VARCHAR}, + + + local_vnc_show_server = #{localVncShowServer,jdbcType=VARCHAR}, + + + local_git_url = #{localGitUrl,jdbcType=VARCHAR}, + + + remote_web_socket = #{remoteWebSocket,jdbcType=VARCHAR}, + + + remote_ssh_nodes = #{remoteSshNodes,jdbcType=VARCHAR}, + + + remote_show_server = #{remoteShowServer,jdbcType=VARCHAR}, + + + remote_vnc_show_server = #{remoteVncShowServer,jdbcType=VARCHAR}, + + + remote_git_url = #{remoteGitUrl,jdbcType=VARCHAR}, + + + white_images = #{whiteImages,jdbcType=VARCHAR}, + + + black_images = #{blackImages,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + update cluster_config + set name = #{name,jdbcType=VARCHAR}, + local = #{local,jdbcType=BIT}, + main_switch = #{mainSwitch,jdbcType=VARCHAR}, + weight = #{weight,jdbcType=DOUBLE}, + auto_scale = #{autoScale,jdbcType=BIT}, + master_url = #{masterUrl,jdbcType=VARCHAR}, + ca_cer_data = #{caCertData,jdbcType=VARCHAR}, + client_cer_data = #{clientCerData,jdbcType=VARCHAR}, + client_key_data = #{clientKeyData,jdbcType=VARCHAR}, + username = #{username,jdbcType=VARCHAR}, + password = #{password,jdbcType=VARCHAR}, + kubeconfig = #{kubeconfig,jdbcType=VARCHAR}, + local_web_socket = #{localWebSocket,jdbcType=VARCHAR}, + local_ssh_nodes = #{localSshNodes,jdbcType=VARCHAR}, + local_show_server = #{localShowServer,jdbcType=VARCHAR}, + local_vnc_show_server = #{localVncShowServer,jdbcType=VARCHAR}, + local_git_url = #{localGitUrl,jdbcType=VARCHAR}, + remote_web_socket = #{remoteWebSocket,jdbcType=VARCHAR}, + remote_ssh_nodes = #{remoteSshNodes,jdbcType=VARCHAR}, + remote_show_server = #{remoteShowServer,jdbcType=VARCHAR}, + remote_vnc_show_server = #{remoteVncShowServer,jdbcType=VARCHAR}, + remote_git_url = #{remoteGitUrl,jdbcType=VARCHAR}, + white_images = #{whiteImages,jdbcType=VARCHAR}, + black_images = #{blackImages,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/ErrorPodInfoMapper.xml b/common/src/main/resources/mybatis/ErrorPodInfoMapper.xml new file mode 100644 index 0000000..b0357e2 --- /dev/null +++ b/common/src/main/resources/mybatis/ErrorPodInfoMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, error_time, pod_name, node_Ip, tpi_ID, build_ID, pod_phase, pod_status_reason, pod_status_message, pod_condition_type, pod_condition_status, pod_condition_reason, pod_condition_message, container_status_ready, container_status_waiting, container_status_waiting_reason, container_status_waiting_message, container_status_terminated, container_status_terminated_reason, container_status_terminated_message, image_exist + + + insert into error_pod_info + + + id, + + + error_time, + + + pod_name, + + + node_Ip, + + + tpi_ID, + + + build_ID, + + + pod_phase, + + + pod_status_reason, + + + pod_status_message, + + + pod_condition_type, + + + pod_condition_status, + + + pod_condition_reason, + + + pod_condition_message, + + + container_status_ready, + + + container_status_waiting, + + + container_status_waiting_reason, + + + container_status_waiting_message, + + + container_status_terminated, + + + container_status_terminated_reason, + + + container_status_terminated_message, + + + image_exist, + + + + + #{id,jdbcType=BIGINT}, + + + #{errorTime,jdbcType=TIMESTAMP}, + + + #{podName,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{tpiID,jdbcType=VARCHAR}, + + + #{buildID,jdbcType=VARCHAR}, + + + #{podPhase,jdbcType=VARCHAR}, + + + #{podStatusReason,jdbcType=VARCHAR}, + + + #{podStatusMessage,jdbcType=VARCHAR}, + + + #{podConditionType,jdbcType=VARCHAR}, + + + #{podConditionStatus,jdbcType=VARCHAR}, + + + #{podConditionReason,jdbcType=VARCHAR}, + + + #{podConditionMessage,jdbcType=VARCHAR}, + + + #{containerStatusReady,jdbcType=VARCHAR}, + + + #{containerStatusWaiting,jdbcType=VARCHAR}, + + + #{containerStatusWaitingReason,jdbcType=VARCHAR}, + + + #{containerStatusWaitingMessage,jdbcType=VARCHAR}, + + + #{containerStatusTerminated,jdbcType=VARCHAR}, + + + #{containerStatusTerminatedReason,jdbcType=VARCHAR}, + + + #{containerStatusTerminatedMessage,jdbcType=VARCHAR}, + + + #{imageExist,jdbcType=VARCHAR}, + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/EvaDayStatMapper.xml b/common/src/main/resources/mybatis/EvaDayStatMapper.xml new file mode 100644 index 0000000..295bf5e --- /dev/null +++ b/common/src/main/resources/mybatis/EvaDayStatMapper.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + id, stat_date, eva_total, pull_slow, full_fail, create_pod_slow, create_pod_fail, exec_fail, others_fail, eva_fail_ratio, eva_timeout, eva_timeout_ratio + + + + + insert into eva_day_stat (id, stat_date, eva_total, pull_slow, pull_fail, + create_pod_slow, create_pod_fail, exec_fail, others_fail, eva_fail_ratio, + eva_timeout, eva_timeout_ratio) + values (#{id,jdbcType=BIGINT}, #{statDate,jdbcType=INTEGER}, #{evaTotal,jdbcType=INTEGER}, + #{pullSlow,jdbcType=INTEGER}, #{pullFail,jdbcType=INTEGER}, #{createPodSlow,jdbcType=INTEGER}, #{createPodFail,jdbcType=INTEGER}, + #{execFail,jdbcType=INTEGER}, #{othersFail,jdbcType=INTEGER}, #{evaFailRatio,jdbcType=DOUBLE}, + #{evaTimeout,jdbcType=INTEGER}, #{evaTimeoutRatio,jdbcType=DOUBLE}) + + + insert into eva_day_stat + + + id, + + + stat_date, + + + eva_total, + + + pull_slow, + + + pull_fail, + + + create_pod_slow, + + + create_pod_fail, + + + exec_fail, + + + others_fail, + + + eva_fail_ratio, + + + eva_timeout, + + + eva_timeout_ratio, + + + + + #{id,jdbcType=BIGINT}, + + + #{statDate,jdbcType=INTEGER}, + + + #{evaTotal,jdbcType=INTEGER}, + + + #{pullSlow,jdbcType=INTEGER}, + + + #{pullFail,jdbcType=INTEGER}, + + + #{createPodSlow,jdbcType=INTEGER}, + + + #{createPodFail,jdbcType=INTEGER}, + + + #{execFail,jdbcType=INTEGER}, + + + #{othersFail,jdbcType=INTEGER}, + + + #{evaFailRatio,jdbcType=DOUBLE}, + + + #{evaTimeout,jdbcType=INTEGER}, + + + #{evaTimeoutRatio,jdbcType=DOUBLE}, + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/OjEvaDayStatMapper.xml b/common/src/main/resources/mybatis/OjEvaDayStatMapper.xml new file mode 100644 index 0000000..a368924 --- /dev/null +++ b/common/src/main/resources/mybatis/OjEvaDayStatMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + id, stat_date, eva_total, eva_fail, eva_fail_ratio, eva_timeout, eva_timeout_ratio + + + insert into oj_eva_day_stat + + + id, + + + stat_date, + + + eva_total, + + + eva_fail, + + + eva_fail_ratio, + + + eva_timeout, + + + eva_timeout_ratio, + + + + + #{id,jdbcType=BIGINT}, + + + #{statDate,jdbcType=INTEGER}, + + + #{evaTotal,jdbcType=INTEGER}, + + + #{evaFail,jdbcType=INTEGER}, + + + #{evaFailRatio,jdbcType=DOUBLE}, + + + #{evaTimeout,jdbcType=INTEGER}, + + + #{evaTimeoutRatio,jdbcType=DOUBLE}, + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/PlatformConfigMapper.xml b/common/src/main/resources/mybatis/PlatformConfigMapper.xml new file mode 100644 index 0000000..ba0bfcd --- /dev/null +++ b/common/src/main/resources/mybatis/PlatformConfigMapper.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + id, platform, containers, script, file_name_suffix, create_time, update_time + + + + insert into platform_config + + + id, + + + platform, + + + containers, + + + create_time, + + + update_time, + + + script, + + + file_name_suffix, + + + + + #{id,jdbcType=BIGINT}, + + + #{platform,jdbcType=VARCHAR}, + + + #{containers,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + #{script,jdbcType=LONGVARCHAR}, + + + #{fileNameSuffix,jdbcType=VARCHAR}, + + + + + + update platform_config + + + platform = #{platform,jdbcType=VARCHAR}, + + + containers = #{containers,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + script = #{script,jdbcType=LONGVARCHAR}, + + + file_name_suffix = #{fileNameSuffix,jdbcType=VARCHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/RunPodMapper.xml b/common/src/main/resources/mybatis/RunPodMapper.xml new file mode 100644 index 0000000..f6d5e4d --- /dev/null +++ b/common/src/main/resources/mybatis/RunPodMapper.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, cluster, name, image_name, + cpu_limit, memory_limit, cpu_request, memory_request, + create_by, node_ip, + run_pod_type, svc_port, ssh_port, + priority, + expire_time, create_time, update_time + + + + + delete from + run_pod + where name = #{name,jdbcType=VARCHAR} + + + delete from + run_pod + where name = #{name,jdbcType=VARCHAR} and expire_time <= #{expireTime,jdbcType=TIMESTAMP} + + + + insert into run_pod (id,cluster, name, + image_name, + cpu_limit, + memory_limit, + cpu_request, + memory_request, + create_by, + node_ip, + run_pod_type, + priority, + svc_port, + ssh_port, + expire_time, + create_time, + update_time) + values + (#{id,jdbcType=BIGINT}, + #{cluster,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR}, + #{imageName,jdbcType=VARCHAR}, + #{cpuLimit,jdbcType=DOUBLE}, + #{memoryLimit,jdbcType=INTEGER}, + #{cpuRequest,jdbcType=DOUBLE}, + #{memoryRequest,jdbcType=INTEGER}, + #{createBy,jdbcType=VARCHAR}, + #{nodeIp,jdbcType=VARCHAR}, + #{runPodType,jdbcType=INTEGER}, + #{priority,jdbcType=BIGINT}, + #{svcPort,jdbcType=VARCHAR}, + #{sshPort,jdbcType=VARCHAR}, + #{expireTime,jdbcType=TIMESTAMP}, + #{createTime,jdbcType=TIMESTAMP}, + #{updateTime,jdbcType=TIMESTAMP}) + + + insert into run_pod + + + id, + + + cluster, + + + name, + + + image_name, + + + cpu_limit, + + + memory_limit, + + + cpu_request, + + + memory_request, + + + create_by, + + + node_ip, + + + run_pod_type, + + + priority, + + + svc_port, + + + ssh_port, + + + expire_time, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{cluster,jdbcType=VARCHAR}, + + + #{name,jdbcType=VARCHAR}, + + + #{imageName,jdbcType=VARCHAR}, + + + #{cpuLimit,jdbcType=DOUBLE}, + + + #{memoryLimit,jdbcType=INTEGER}, + + + #{cpuRequest,jdbcType=DOUBLE}, + + + #{memoryRequest,jdbcType=INTEGER}, + + + #{createBy,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{runPodType,jdbcType=INTEGER}, + + + #{priority,jdbcType=BIGINT}, + + + #{svcPort,jdbcType=VARCHAR}, + + + #{sshPort,jdbcType=VARCHAR}, + + + #{expireTime,jdbcType=TIMESTAMP}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update run_pod + + + name = #{name,jdbcType=VARCHAR}, + + + cluster = #{cluster,jdbcType=VARCHAR}, + + + image_name = #{imageName,jdbcType=VARCHAR}, + + + cpu_limit = #{cpuLimit,jdbcType=DOUBLE}, + + + memory_limit = #{memoryLimit,jdbcType=INTEGER}, + + + cpu_request = #{cpuRequest,jdbcType=DOUBLE}, + + + memory_request = #{memoryRequest,jdbcType=INTEGER}, + + + create_by = #{createBy,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + run_pod_type = #{runPodType,jdbcType=INTEGER}, + + + priority = #{priority,jdbcType=BIGINT}, + + + svc_port = #{svcPort,jdbcType=VARCHAR}, + + + ssh_port = #{sshPort,jdbcType=VARCHAR}, + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + + update run_pod + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where name = #{name,jdbcType=VARCHAR} + + + + update run_pod set expire_time = #{expireTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where name = #{name,jdbcType=VARCHAR} and expire_time < #{expireTime,jdbcType=TIMESTAMP} + + + + + update run_pod + set name = #{name,jdbcType=VARCHAR}, + cluster = #{cluster,jdbcType=VARCHAR}, + image_name = + #{imageName,jdbcType=VARCHAR}, + cpu_limit = + #{cpuLimit,jdbcType=DOUBLE}, + memory_limit = + #{memoryLimit,jdbcType=INTEGER}, + cpu_request = + #{cpuRequest,jdbcType=DOUBLE}, + memory_request = + #{memoryRequest,jdbcType=INTEGER}, + create_by = + #{createBy,jdbcType=VARCHAR}, + node_ip = + #{nodeIp,jdbcType=VARCHAR}, + run_pod_type = + #{runPodType,jdbcType=INTEGER}, + priority = #{priority,jdbcType=BIGINT}, + svc_port = #{svcPort,jdbcType=VARCHAR}, + ssh_port = + #{sshPort,jdbcType=VARCHAR}, + expire_time = + #{expireTime,jdbcType=TIMESTAMP}, + create_time + =#{createTime,jdbcType=TIMESTAMP}, + update_time + =#{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + + + + + + + + + UPDATE + run_pod + + + svc_port = #{svcPort,jdbcType=VARCHAR}, + + + ssh_port = #{sshPort,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + WHERE + name = #{name,jdbcType=VARCHAR} + + + UPDATE + run_pod + + + expire_time = #{expireTime,jdbcType=VARCHAR}, + + + WHERE + cluster = #{cluster,jdbcType=VARCHAR} + and node_ip = #{nodeIp,jdbcType=VARCHAR} + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SecurityContextConfigMapper.xml b/common/src/main/resources/mybatis/SecurityContextConfigMapper.xml new file mode 100644 index 0000000..e4db894 --- /dev/null +++ b/common/src/main/resources/mybatis/SecurityContextConfigMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + id, cluster, image_name, privileged, add_cap, drop_cap, create_time, update_time + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysConfigMapper.xml b/common/src/main/resources/mybatis/SysConfigMapper.xml new file mode 100644 index 0000000..2f7d37c --- /dev/null +++ b/common/src/main/resources/mybatis/SysConfigMapper.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + id, name, val, expire_in, expire_time, create_time, + update_time, status + + + + + insert into sys_config (id, name, val, + expire_in, + expire_time, create_time, + update_time, status + values + (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, + #{val,jdbcType=VARCHAR}, + #{expireIn,jdbcType=INTEGER}, + #{expireTime,jdbcType=TIMESTAMP}, #{createTime,jdbcType=TIMESTAMP}, + #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=CHAR}) + + + insert into sys_config + + + id, + + + name, + + + val, + + + expire_in, + + + expire_time, + + + create_time, + + + update_time, + + + status, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{val,jdbcType=VARCHAR}, + + + #{expireIn,jdbcType=INTEGER}, + + + #{expireTime,jdbcType=TIMESTAMP}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + #{status,jdbcType=CHAR}, + + + + + update sys_config + + + name = #{name,jdbcType=VARCHAR}, + + + val = #{val,jdbcType=VARCHAR}, + + + expire_in = #{expireIn,jdbcType=INTEGER}, + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + status = #{status,jdbcType=CHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + update sys_config + set name = #{name,jdbcType=VARCHAR}, + val = #{val,jdbcType=VARCHAR}, + expire_in = + #{expireIn,jdbcType=INTEGER}, + expire_time = + #{expireTime,jdbcType=TIMESTAMP}, + create_time = + #{createTime,jdbcType=TIMESTAMP}, + update_time = + #{updateTime,jdbcType=TIMESTAMP}, + status = #{status,jdbcType=CHAR} + where id = #{id,jdbcType=BIGINT} + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysMenuMapper.xml b/common/src/main/resources/mybatis/SysMenuMapper.xml new file mode 100644 index 0000000..5209a4e --- /dev/null +++ b/common/src/main/resources/mybatis/SysMenuMapper.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysRoleMapper.xml b/common/src/main/resources/mybatis/SysRoleMapper.xml new file mode 100644 index 0000000..3d40def --- /dev/null +++ b/common/src/main/resources/mybatis/SysRoleMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysRoleMenuMapper.xml b/common/src/main/resources/mybatis/SysRoleMenuMapper.xml new file mode 100644 index 0000000..f45f534 --- /dev/null +++ b/common/src/main/resources/mybatis/SysRoleMenuMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + delete from edu_sys_role_menu where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysTokenMapper.xml b/common/src/main/resources/mybatis/SysTokenMapper.xml new file mode 100644 index 0000000..c15b2c7 --- /dev/null +++ b/common/src/main/resources/mybatis/SysTokenMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysUserMapper.xml b/common/src/main/resources/mybatis/SysUserMapper.xml new file mode 100644 index 0000000..7acc16b --- /dev/null +++ b/common/src/main/resources/mybatis/SysUserMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/SysUserRoleMapper.xml b/common/src/main/resources/mybatis/SysUserRoleMapper.xml new file mode 100644 index 0000000..4cc5213 --- /dev/null +++ b/common/src/main/resources/mybatis/SysUserRoleMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + delete from edu_sys_user_role where role_id in + + #{roleId} + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/mybatis/WindowsInfoMapper.xml b/common/src/main/resources/mybatis/WindowsInfoMapper.xml new file mode 100644 index 0000000..f9f6068 --- /dev/null +++ b/common/src/main/resources/mybatis/WindowsInfoMapper.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + id, uniq_id, instance_id, user_id, port, vnc_port, template_name, auto_release_time, forward_table_id, forward_entry_id, vnc_forward_entry_id, status + + + + + + + + + + + + + update windows_info set status = -1 + where uniq_id = #{uniqId,jdbcType=VARCHAR} + + + + insert into windows_info + + + id, + + + uniq_id, + + + instance_id, + + + user_id, + + + port, + + + vnc_port, + + + template_name, + + + auto_release_time, + + + forward_table_id, + + + forward_entry_id, + + + vnc_forward_entry_id, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{uniqId,jdbcType=VARCHAR}, + + + #{instanceId,jdbcType=VARCHAR}, + + + #{userID,jdbcType=VARCHAR}, + + + #{port,jdbcType=VARCHAR}, + + + #{vncPort,jdbcType=VARCHAR}, + + + #{templateName,jdbcType=VARCHAR}, + + + #{autoReleaseTime,jdbcType=TIMESTAMP}, + + + #{forwardTableId,jdbcType=VARCHAR}, + + + #{forwardEntryId,jdbcType=VARCHAR}, + + + #{vncForwardEntryId,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update windows_info + + + instance_id = #{instanceId,jdbcType=VARCHAR}, + + + user_id = #{userID,jdbcType=VARCHAR}, + + + port = #{port,jdbcType=INTEGER}, + + + vnc_port = #{vncPort,jdbcType=INTEGER}, + + + template_name = #{templateName,jdbcType=VARCHAR}, + + + auto_release_time = #{autoReleaseTime,jdbcType=TIMESTAMP}, + + + forward_table_id = #{forwardTableId,jdbcType=VARCHAR}, + + + forward_entry_id = #{forwardEntryId,jdbcType=VARCHAR}, + + + vnc_forward_entry_id = #{vncForwardEntryId,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where uniq_id = #{uniqId,jdbcType=VARCHAR} + + \ No newline at end of file diff --git a/common/target/classes/META-INF/spring-configuration-metadata.json b/common/target/classes/META-INF/spring-configuration-metadata.json new file mode 100644 index 0000000..b496a1c --- /dev/null +++ b/common/target/classes/META-INF/spring-configuration-metadata.json @@ -0,0 +1,27 @@ +{ + "groups": [ + { + "name": "mystarter.config.student", + "type": "com.imitate.common.test.PersonConfigProperties", + "sourceType": "com.imitate.common.test.PersonConfigProperties" + } + ], + "properties": [ + { + "name": "mystarter.config.student.age", + "type": "java.lang.Integer", + "sourceType": "com.imitate.common.test.PersonConfigProperties" + }, + { + "name": "mystarter.config.student.gender", + "type": "java.lang.String", + "sourceType": "com.imitate.common.test.PersonConfigProperties" + }, + { + "name": "mystarter.config.student.name", + "type": "java.lang.String", + "sourceType": "com.imitate.common.test.PersonConfigProperties" + } + ], + "hints": [] +} \ No newline at end of file diff --git a/common/target/classes/common.properties b/common/target/classes/common.properties new file mode 100644 index 0000000..578d8f8 --- /dev/null +++ b/common/target/classes/common.properties @@ -0,0 +1,142 @@ +mystarter.config.enable=true +mystarter.config.student.name=yc +mystarter.config.student.age=18 +mystarter.config.student.gender=man + + + + +useMq=true +netNames=en0 +#web\u56de\u8c03\u5730\u5740 +educoderURL=https://www.educoder.net/api/myshixuns/training_task_status.json +simpileEducoderURL=https://www.educoder.net/api/myshixuns/code_runinng_message.json +ojEvaCallBackURL=https://www.educoder.net/api/myproblems/:tpiid/listen_result.json + +#git\u670d\u52a1\u5668\u57df\u540d\uff0c\u8fdc\u7a0b\u96c6\u7fa4\u9700\u8981\u4f7f\u7528\u8fd9\u4e2a\u57df\u540d\u6765\u4e0b\u8f7d +gitServiceDomain=https://git.educoder.net + +#\u6d4b\u8bd5\u7248dockerURL(\u4e3b\u8282\u70b9) +dockerMaster=http://172.16.94.148:4243 +#\u6d4b\u8bd5\u7248dockerURL(\u526f\u8282\u70b9) +dockerDeputies=http://172.16.94.167:4243;http://172.16.94.169:4243;http://172.16.94.172:4243 + +#\u955c\u50cf\u4ed3\u5e93\u5730\u5740 +registry=BRI_DOCKER_REGISTRY + + +#GIT_IP +gitIP=172.16.94.154 +gitBackUpIP=172.16.94.154 +gitPort=30122 +gitBackupDirs=/opt/repositories/2018,/opt/repositories/2018-2 +gitRepoDir=/opt/repositories +#git\u7528\u6237\u540d\u53ca\u5bc6\u7801 +gitUsername=edugit +gitPassword=xinedugit# + +workspace=/opt/workspace +secretWorkspace=/opt/workspace/secret +datadir=/opt/shixunfiles +bigDataDir=/opt/bigfiles + + + +#GPU \u955c\u50cf +gpuImage=nvidia +gpuBigImage=gpu-big +#shenlong \u955c\u50cf\u7279\u6027 +shenlong.image.character=shenlong + +#novnc port +vncPort=6901 + +#jupyter \u76f8\u5173\u914d\u7f6e +jupyterPort=80 +jupyterSurvivalMinute=60 +jupyterDelayMinute=20 +tpmJupyterDelayMinute=120 +tpmJupyterSurvivalMinute=120 + +webssh.delayMinute=21 + +eva.MaxPoolSize=800 +eva.HttpMaxConnTotal=800 +eva.HttpMaxConnPerRoute=200 +git.HttpMaxConnTotal=400 +git.HttpMaxConnPerRoute=400 +k8s.MaxConcurrentRequests=640 +k8s.MaxConcurrentRequestsPerHost=50 + +windows.forwardTableId=ftb-2zepf6ofbt3qitk1jevs0 +windows.externalIp=39.105.62.120 +windows.username=administrator +windows.password=Edu_123123 +windows.workspace=C:/Windows/AppReadiness/ +windows.initTime=3600 +windows.delayTime=3600 +linux.username=root + + +testCaseDir=/opt/bigfiles/filecase +testCaseActualOutDir=/opt/workspace/filecase + + + + +#limit of running pod number +maxRunningPodNum=20000 +#\u6d4b\u8bd5\u7248\u8d44\u6e90\u603b\u91cf +cpuAllocatable=4000 +memAllocatable=10000 + + +# defaultTimeLimit of executing time in a pod. +defaultTimeLimit=90 +# delay delete time +evaluate.pod.pass.delayDeleteTime=0 +evaluate.pod.default.delayDeleteTime=60 +#\u7f16\u8bd1\u7ed3\u679c\u6700\u5927\u957f\u5ea6 +outputSize=65535 + + + + +# sophgo +sophgo.apiKey=touge@126.com +sophgo.apiSecret=y2odnlm44zsbmd0j1aznzzqrev5w76cj +# sophgo API address +sophgo.api.domain=http://175.102.182.50:19028 +# sophgo sub ???????? url ???? +sophgo.api.notifyUrl=http://pre-bridge.educoder.net/bridge/sophgo/notify +sophgo.delayTime=3600 + +# nfs? +workspace.nfs.configs=/opt/workspace,/opt/workspace2 +bridge.errorOutPuts=\u7edf\u7e41\u5fd9\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\n\ +\u7a0b\u5e8f\u6267\u884c\u5931\u8d25\u5bfc\u81f4\u8bc4\u6d4b\u63d0\u524d\u7ec8\u6b62\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u6216\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\n\ +\u6b21\u8bc4\u6d4b\u7f51\u7edc\u5ef6\u8fdf\u8f83\u9ad8\uff0c\u8d44\u6e90\u65e0\u6cd5\u6b63\u5e38\u52a0\u8f7d\uff0c\u8bf7\u0031\u0030\u0073\u540e\u91cd\u8bd5\n\ +\u5f53\u524d\u5b9e\u9a8c\u73af\u5883\u6b63\u5728\u66f4\u65b0\u4e2d\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u6216\u8054\u7cfb\u7cfb\u7edf\u7ba1\u7406\u5458\uff01\n\ +\u5f53\u524d\u7f51\u7edc\u8f83\u5dee\uff0c\u4ee3\u7801\u4e0b\u8f7d\u8d85\u65f6\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\uff01\n\ +\u8bc4\u6d4b\u811a\u672c\u8bbe\u7f6e\u5f02\u5e38\uff0c\u5efa\u8bae\u60a8\u5728\u5b9e\u8bad\u7684\u914d\u7f6e\u9875\u9762\u91cd\u65b0\u9009\u62e9\u6216\u4fee\u6539\u8bc4\u6d4b\u811a\u672c + +# ??????? +dingding.bridge.evaluation.token=1069c45d4dfab187bb95599faeaf3687b630f79eda0d6fff914a18ee9e25db6f +dingding.bridge.evaluation.secret=SEC7431097248ee9c19b9d7a2c61883e29082bea79c88c3e8f02e5fa121c2837dbc + +# ?????client?? +aliyun.flex.accessKeyId=LTAI5tG6z2pg3yQ42RQZLUKc +aliyun.flex.accessKeySecret=EzNHWle4HDZERwnc5syBPV86PZLmow +# ????id +aliyun.flex.ruleAri=ari:acs:ess:cn-hangzhou:1829848226361863:scalingrule/asr-bp1cozzmradkyct0cntj + +#\u8ba4\u8bc1\u5bc6\u94a5 +secretKey=priEn3UwXfJs3PmyXnSG + +#vscode?????? ???? codeserver1.vnc.educoder.net +vscodeDomain=testcodeserver1.vnc.educoder.net + + +spring.config.import=classpath:cron.properties + + diff --git a/common/target/classes/cron.properties b/common/target/classes/cron.properties new file mode 100644 index 0000000..3b8b008 --- /dev/null +++ b/common/target/classes/cron.properties @@ -0,0 +1,48 @@ +# \u5237\u65b0\u96c6\u7fa4\u914d\u7f6e\u4fe1\u606f +ClusterConfigTask=*/5 * * * * ? + +# \u5237\u65b0\u96c6\u7fa4\u8282\u70b9\u4fe1\u606f +ClusterTask=*/5 * * * * ? + +# \u5237\u65b0platform_config +RefreshPlatformConfigTask=0 */1 * * * ? + +# \u5237\u65b0\u8d44\u6e90\u4f7f\u7528\u7387 +RefreshNodeResUsageTask=*/5 * * * * ? + +# \u5237\u65b0sys_config +RefreshSysConfigTask=*/5 * * * * ? + +# \u5237\u65b0SecurityContextConfig +RefreshSecurityContextConfigTask=*/5 * * * * ? + +# \u5220\u9664oj\u8bc4\u6d4b\u6570\u7edf\u8ba1 +DelOjEvaStatTask=0 0 3 * * ? + +# \u6e05\u7406oj pod +DelOjPodTask=0 40 5 * * ? + +# \u6e05\u7406\u8d85\u65f6\u9057\u6f0fpod +DelMissPodTask=0 10 3 * * ? + +# \u8bc4\u6d4b\u6bcf\u65e5\u7edf\u8ba1\u4efb\u52a1 +EvaDayStatTask=0 20 3 * * ? + +DelHisCreateImageTask=0 30 3 * * ? + +DelHisK8sNodeImageTask=0 40 3 * * ? + +RefreshNodeStatus=*/10 * * * * ? + +RefreshNodePreReduce=*/10 * * * * ? + +RefreshNodeErrorCheck=*/30 * * * * ? + +ForwardEntryCheckTask=*/10 * * * * ? + +ForwardEntryDelete=0 20 3 * * ? + +ReplayLogSaveToDb=0 0/3 * * * ? + + +SophgoSshTask=0 0/25 * * * ? \ No newline at end of file diff --git a/common/target/classes/mybatis/BridgePodMapper.xml b/common/target/classes/mybatis/BridgePodMapper.xml new file mode 100644 index 0000000..b4a85f0 --- /dev/null +++ b/common/target/classes/mybatis/BridgePodMapper.xml @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, name, tpi_ID, build_ID, uid, k8s_create_time, delete_time, + sec_key, + request_time, + pull, create_pod, + execute, evaluate_all_time, + node_name, + node_ip, download_status, create_pod_status, + compile_status, + run_status, status, image_name, image_version, + cpu_limit, memory_limit, cpu_request, memory_request + + + + + insert into bridge_pod (id, name, tpi_ID, build_ID, uid, + k8s_create_time, delete_time, sec_key, + request_time, pull, create_pod, + execute, evaluate_all_time, node_name, + node_ip, download_status, + create_pod_status, + compile_status, run_status, status, + image_version, + image_name, + cpu_limit, + memory_limit, + cpu_request, + memory_request + ) + values + (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, + #{tpiID,jdbcType=VARCHAR}, + #{buildID,jdbcType=VARCHAR}, + #{uid,jdbcType=VARCHAR}, + #{k8sCreateTime,jdbcType=TIMESTAMP}, + #{deleteTime,jdbcType=TIMESTAMP}, + #{secKey,jdbcType=VARCHAR}, + #{requestTime,jdbcType=TIMESTAMP}, + #{pull,jdbcType=DOUBLE}, + #{createPod,jdbcType=DOUBLE}, + #{execute,jdbcType=DOUBLE}, + #{evaluateAllTime,jdbcType=DOUBLE}, + #{nodeName,jdbcType=VARCHAR}, + #{nodeIp,jdbcType=VARCHAR}, + #{downloadStatus,jdbcType=CHAR}, + #{createPodStatus,jdbcType=CHAR}, + #{compileStatus,jdbcType=CHAR}, + #{runStatus,jdbcType=CHAR}, + #{status,jdbcType=CHAR}, + #{imageName,jdbcType=VARCHAR}, + #{imageVersion,jdbcType=VARCHAR}, + #{cpuLimit,jdbcType=DOUBLE}, + #{memoryLimit,jdbcType=INTEGER}, + #{cpuRequest,jdbcType=DOUBLE}, + #{memoryRequest,jdbcType=INTEGER} + ) + + + insert into bridge_pod + + + id, + + + name, + + + tpi_ID, + + + build_ID, + + + uid, + + + k8s_create_time, + + + delete_time, + + + sec_key, + + + request_time, + + + pull, + + + create_pod, + + + execute, + + + evaluate_all_time, + + + node_name, + + + node_ip, + + + download_status, + + + create_pod_status, + + + compile_status, + + + run_status, + + + status, + + + image_name, + + + image_version, + + + cpu_limit, + + + memory_limit, + + + cpu_request, + + + memory_request, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{tpiID,jdbcType=VARCHAR}, + + + #{buildID,jdbcType=VARCHAR}, + + + #{uid,jdbcType=VARCHAR}, + + + #{k8sCreateTime,jdbcType=TIMESTAMP}, + + + #{deleteTime,jdbcType=TIMESTAMP}, + + + #{secKey,jdbcType=VARCHAR}, + + + #{requestTime,jdbcType=TIMESTAMP}, + + + #{pull,jdbcType=DOUBLE}, + + + #{createPod,jdbcType=DOUBLE}, + + + #{execute,jdbcType=DOUBLE}, + + + #{evaluateAllTime,jdbcType=DOUBLE}, + + + #{nodeName,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{downloadStatus,jdbcType=CHAR}, + + + #{createPodStatus,jdbcType=CHAR}, + + + #{compileStatus,jdbcType=CHAR}, + + + #{runStatus,jdbcType=CHAR}, + + + #{status,jdbcType=CHAR}, + + + #{imageName,jdbcType=VARCHAR}, + + + #{imageVersion,jdbcType=VARCHAR}, + + + #{cpuLimit,jdbcType=DOUBLE}, + + + #{memoryLimit,jdbcType=INTEGER}, + + + #{cpuRequest,jdbcType=DOUBLE}, + + + #{memoryRequest,jdbcType=INTEGER}, + + + + + update bridge_pod + + + name = #{name,jdbcType=VARCHAR}, + + + tpi_ID = #{tpiID,jdbcType=VARCHAR}, + + + build_ID = #{buildID,jdbcType=VARCHAR}, + + + uid = #{uid,jdbcType=VARCHAR}, + + + k8s_create_time = #{k8sCreateTime,jdbcType=TIMESTAMP}, + + + delete_time = #{deleteTime,jdbcType=TIMESTAMP}, + + + sec_key = #{secKey,jdbcType=VARCHAR}, + + + request_time = #{requestTime,jdbcType=TIMESTAMP}, + + + pull = #{pull,jdbcType=DOUBLE}, + + + create_pod = #{createPod,jdbcType=DOUBLE}, + + + execute = #{execute,jdbcType=DOUBLE}, + + + evaluate_all_time = #{evaluateAllTime,jdbcType=DOUBLE}, + + + node_name = #{nodeName,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + download_status = #{downloadStatus,jdbcType=CHAR}, + + + create_pod_status = #{createPodStatus,jdbcType=CHAR}, + + + compile_status = #{compileStatus,jdbcType=CHAR}, + + + run_status = #{runStatus,jdbcType=CHAR}, + + + status = #{status,jdbcType=CHAR}, + + + image_name = #{imageName,jdbcType=VARCHAR}, + + + image_version = #{imageVersion,jdbcType=VARCHAR}, + + + cpu_limit = #{cpuLimit,jdbcType=DOUBLE}, + + + memory_limit = #{memoryLimit,jdbcType=INTEGER}, + + + cpu_request = #{cpuRequest,jdbcType=DOUBLE}, + + + memory_request = #{memoryRequest,jdbcType=INTEGER}, + + + where id = #{id,jdbcType=BIGINT} + + + update bridge_pod + set name = #{name,jdbcType=VARCHAR}, + tpi_ID = #{tpiID,jdbcType=VARCHAR}, + build_ID = #{buildID,jdbcType=VARCHAR}, + uid = #{uid,jdbcType=VARCHAR}, + k8s_create_time = + #{k8sCreateTime,jdbcType=TIMESTAMP}, + delete_time = + #{deleteTime,jdbcType=TIMESTAMP}, + sec_key = #{secKey,jdbcType=VARCHAR}, + request_time = #{requestTime,jdbcType=TIMESTAMP}, + pull = + #{pull,jdbcType=DOUBLE}, + create_pod = #{createPod,jdbcType=DOUBLE}, + execute = #{execute,jdbcType=DOUBLE}, + evaluate_all_time = + #{evaluateAllTime,jdbcType=DOUBLE}, + node_name = + #{nodeName,jdbcType=VARCHAR}, + node_ip = #{nodeIp,jdbcType=VARCHAR}, + download_status = #{downloadStatus,jdbcType=CHAR}, + create_pod_status = + #{createPodStatus,jdbcType=CHAR}, + compile_status = + #{compileStatus,jdbcType=CHAR}, + run_status = + #{runStatus,jdbcType=CHAR}, + status = #{status,jdbcType=CHAR}, + image_name = + #{imageName,jdbcType=VARCHAR}, + image_version = + #{imageVersion,jdbcType=VARCHAR}, + cpu_limit = + #{cpuLimit,jdbcType=DOUBLE}, + memory_limit = + #{memoryLimit,jdbcType=INTEGER}, + cpu_request = + #{cpuRequest,jdbcType=DOUBLE}, + memory_request = + #{memoryRequest,jdbcType=INTEGER} + where id = + #{id,jdbcType=BIGINT} + + + + + update bridge_pod + set delete_time = #{deleteTime,jdbcType=TIMESTAMP} + where name = #{name,jdbcType=VARCHAR} and delete_time is null + + and request_time >= #{requestTime,jdbcType=TIMESTAMP} + + + + + delete from + bridge_pod + where request_time < #{requestTime,jdbcType=TIMESTAMP} + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/ClusterConfigMapper.xml b/common/target/classes/mybatis/ClusterConfigMapper.xml new file mode 100644 index 0000000..392ba14 --- /dev/null +++ b/common/target/classes/mybatis/ClusterConfigMapper.xml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, name, local, main_switch, weight, auto_scale, master_url, ca_cert_data, client_cer_data, client_key_data, username, password, + kubeconfig, local_web_socket, local_ssh_nodes, local_show_server, local_vnc_show_server, + local_git_url, remote_web_socket, remote_ssh_nodes, remote_show_server, remote_vnc_show_server, + remote_git_url, white_images, black_images, create_time, update_time + + + + + insert into cluster_config (id, name, local, main_switch, + weight, auto_scale, master_url, ca_cert_data, client_cer_data, client_key_data, + username, password, kubeconfig, + local_web_socket, local_ssh_nodes, local_show_server, + local_vnc_show_server, local_git_url, remote_web_socket, + remote_ssh_nodes, remote_show_server, remote_vnc_show_server, + remote_git_url, white_images, black_images, + create_time, update_time) + values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{local,jdbcType=BIT}, #{mainSwitch,jdbcType=VARCHAR}, + #{weight,jdbcType=DOUBLE}, #{autoScale,jdbcType=BIT}, #{masterUrl,jdbcType=VARCHAR}, + #{caCertData,jdbcType=VARCHAR}, #{clientCerData,jdbcType=VARCHAR}, #{clientKeyData,jdbcType=VARCHAR}, + #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{kubeconfig,jdbcType=VARCHAR}, + #{localWebSocket,jdbcType=VARCHAR}, #{localSshNodes,jdbcType=VARCHAR}, #{localShowServer,jdbcType=VARCHAR}, + #{localVncShowServer,jdbcType=VARCHAR}, #{localGitUrl,jdbcType=VARCHAR}, #{remoteWebSocket,jdbcType=VARCHAR}, + #{remoteSshNodes,jdbcType=VARCHAR}, #{remoteShowServer,jdbcType=VARCHAR}, #{remoteVncShowServer,jdbcType=VARCHAR}, + #{remoteGitUrl,jdbcType=VARCHAR}, #{whiteImages,jdbcType=VARCHAR}, #{blackImages,jdbcType=VARCHAR}, + #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) + + + insert into cluster_config + + + id, + + + name, + + + local, + + + main_switch, + + + weight, + + + auto_scale, + + + master_url, + + + ca_cert_data, + + + client_cer_data, + + + client_key_data, + + + username, + + + password, + + + kubeconfig, + + + local_web_socket, + + + local_ssh_nodes, + + + local_show_server, + + + local_vnc_show_server, + + + local_git_url, + + + remote_web_socket, + + + remote_ssh_nodes, + + + remote_show_server, + + + remote_vnc_show_server, + + + remote_git_url, + + + white_images, + + + black_images, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{local,jdbcType=BIT}, + + + #{mainSwitch,jdbcType=VARCHAR}, + + + #{weight,jdbcType=DOUBLE}, + + + #{autoScale,jdbcType=BIT}, + + + #{masterUrl,jdbcType=VARCHAR}, + + + #{caCertData,jdbcType=VARCHAR}, + + + #{clientCerData,jdbcType=VARCHAR}, + + + #{clientKeyData,jdbcType=VARCHAR}, + + + #{username,jdbcType=VARCHAR}, + + + #{password,jdbcType=VARCHAR}, + + + #{kubeconfig,jdbcType=VARCHAR}, + + + #{localWebSocket,jdbcType=VARCHAR}, + + + #{localSshNodes,jdbcType=VARCHAR}, + + + #{localShowServer,jdbcType=VARCHAR}, + + + #{localVncShowServer,jdbcType=VARCHAR}, + + + #{localGitUrl,jdbcType=VARCHAR}, + + + #{remoteWebSocket,jdbcType=VARCHAR}, + + + #{remoteSshNodes,jdbcType=VARCHAR}, + + + #{remoteShowServer,jdbcType=VARCHAR}, + + + #{remoteVncShowServer,jdbcType=VARCHAR}, + + + #{remoteGitUrl,jdbcType=VARCHAR}, + + + #{whiteImages,jdbcType=VARCHAR}, + + + #{blackImages,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update cluster_config + + + name = #{name,jdbcType=VARCHAR}, + + + local = #{local,jdbcType=BIT}, + + + main_switch = #{mainSwitch,jdbcType=VARCHAR}, + + + weight = #{weight,jdbcType=DOUBLE}, + + + auto_scale = #{autoScale,jdbcType=BIT}, + + + master_url = #{masterUrl,jdbcType=VARCHAR}, + + + ca_cert_data = #{caCertData,jdbcType=VARCHAR}, + + + client_cer_data = #{clientCerData,jdbcType=VARCHAR}, + + + client_key_data = #{clientKeyData,jdbcType=VARCHAR}, + + + username = #{username,jdbcType=VARCHAR}, + + + password = #{password,jdbcType=VARCHAR}, + + + kubeconfig = #{kubeconfig,jdbcType=VARCHAR}, + + + local_web_socket = #{localWebSocket,jdbcType=VARCHAR}, + + + local_ssh_nodes = #{localSshNodes,jdbcType=VARCHAR}, + + + local_show_server = #{localShowServer,jdbcType=VARCHAR}, + + + local_vnc_show_server = #{localVncShowServer,jdbcType=VARCHAR}, + + + local_git_url = #{localGitUrl,jdbcType=VARCHAR}, + + + remote_web_socket = #{remoteWebSocket,jdbcType=VARCHAR}, + + + remote_ssh_nodes = #{remoteSshNodes,jdbcType=VARCHAR}, + + + remote_show_server = #{remoteShowServer,jdbcType=VARCHAR}, + + + remote_vnc_show_server = #{remoteVncShowServer,jdbcType=VARCHAR}, + + + remote_git_url = #{remoteGitUrl,jdbcType=VARCHAR}, + + + white_images = #{whiteImages,jdbcType=VARCHAR}, + + + black_images = #{blackImages,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + update cluster_config + set name = #{name,jdbcType=VARCHAR}, + local = #{local,jdbcType=BIT}, + main_switch = #{mainSwitch,jdbcType=VARCHAR}, + weight = #{weight,jdbcType=DOUBLE}, + auto_scale = #{autoScale,jdbcType=BIT}, + master_url = #{masterUrl,jdbcType=VARCHAR}, + ca_cer_data = #{caCertData,jdbcType=VARCHAR}, + client_cer_data = #{clientCerData,jdbcType=VARCHAR}, + client_key_data = #{clientKeyData,jdbcType=VARCHAR}, + username = #{username,jdbcType=VARCHAR}, + password = #{password,jdbcType=VARCHAR}, + kubeconfig = #{kubeconfig,jdbcType=VARCHAR}, + local_web_socket = #{localWebSocket,jdbcType=VARCHAR}, + local_ssh_nodes = #{localSshNodes,jdbcType=VARCHAR}, + local_show_server = #{localShowServer,jdbcType=VARCHAR}, + local_vnc_show_server = #{localVncShowServer,jdbcType=VARCHAR}, + local_git_url = #{localGitUrl,jdbcType=VARCHAR}, + remote_web_socket = #{remoteWebSocket,jdbcType=VARCHAR}, + remote_ssh_nodes = #{remoteSshNodes,jdbcType=VARCHAR}, + remote_show_server = #{remoteShowServer,jdbcType=VARCHAR}, + remote_vnc_show_server = #{remoteVncShowServer,jdbcType=VARCHAR}, + remote_git_url = #{remoteGitUrl,jdbcType=VARCHAR}, + white_images = #{whiteImages,jdbcType=VARCHAR}, + black_images = #{blackImages,jdbcType=VARCHAR}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/ErrorPodInfoMapper.xml b/common/target/classes/mybatis/ErrorPodInfoMapper.xml new file mode 100644 index 0000000..b0357e2 --- /dev/null +++ b/common/target/classes/mybatis/ErrorPodInfoMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, error_time, pod_name, node_Ip, tpi_ID, build_ID, pod_phase, pod_status_reason, pod_status_message, pod_condition_type, pod_condition_status, pod_condition_reason, pod_condition_message, container_status_ready, container_status_waiting, container_status_waiting_reason, container_status_waiting_message, container_status_terminated, container_status_terminated_reason, container_status_terminated_message, image_exist + + + insert into error_pod_info + + + id, + + + error_time, + + + pod_name, + + + node_Ip, + + + tpi_ID, + + + build_ID, + + + pod_phase, + + + pod_status_reason, + + + pod_status_message, + + + pod_condition_type, + + + pod_condition_status, + + + pod_condition_reason, + + + pod_condition_message, + + + container_status_ready, + + + container_status_waiting, + + + container_status_waiting_reason, + + + container_status_waiting_message, + + + container_status_terminated, + + + container_status_terminated_reason, + + + container_status_terminated_message, + + + image_exist, + + + + + #{id,jdbcType=BIGINT}, + + + #{errorTime,jdbcType=TIMESTAMP}, + + + #{podName,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{tpiID,jdbcType=VARCHAR}, + + + #{buildID,jdbcType=VARCHAR}, + + + #{podPhase,jdbcType=VARCHAR}, + + + #{podStatusReason,jdbcType=VARCHAR}, + + + #{podStatusMessage,jdbcType=VARCHAR}, + + + #{podConditionType,jdbcType=VARCHAR}, + + + #{podConditionStatus,jdbcType=VARCHAR}, + + + #{podConditionReason,jdbcType=VARCHAR}, + + + #{podConditionMessage,jdbcType=VARCHAR}, + + + #{containerStatusReady,jdbcType=VARCHAR}, + + + #{containerStatusWaiting,jdbcType=VARCHAR}, + + + #{containerStatusWaitingReason,jdbcType=VARCHAR}, + + + #{containerStatusWaitingMessage,jdbcType=VARCHAR}, + + + #{containerStatusTerminated,jdbcType=VARCHAR}, + + + #{containerStatusTerminatedReason,jdbcType=VARCHAR}, + + + #{containerStatusTerminatedMessage,jdbcType=VARCHAR}, + + + #{imageExist,jdbcType=VARCHAR}, + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/EvaDayStatMapper.xml b/common/target/classes/mybatis/EvaDayStatMapper.xml new file mode 100644 index 0000000..295bf5e --- /dev/null +++ b/common/target/classes/mybatis/EvaDayStatMapper.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + id, stat_date, eva_total, pull_slow, full_fail, create_pod_slow, create_pod_fail, exec_fail, others_fail, eva_fail_ratio, eva_timeout, eva_timeout_ratio + + + + + insert into eva_day_stat (id, stat_date, eva_total, pull_slow, pull_fail, + create_pod_slow, create_pod_fail, exec_fail, others_fail, eva_fail_ratio, + eva_timeout, eva_timeout_ratio) + values (#{id,jdbcType=BIGINT}, #{statDate,jdbcType=INTEGER}, #{evaTotal,jdbcType=INTEGER}, + #{pullSlow,jdbcType=INTEGER}, #{pullFail,jdbcType=INTEGER}, #{createPodSlow,jdbcType=INTEGER}, #{createPodFail,jdbcType=INTEGER}, + #{execFail,jdbcType=INTEGER}, #{othersFail,jdbcType=INTEGER}, #{evaFailRatio,jdbcType=DOUBLE}, + #{evaTimeout,jdbcType=INTEGER}, #{evaTimeoutRatio,jdbcType=DOUBLE}) + + + insert into eva_day_stat + + + id, + + + stat_date, + + + eva_total, + + + pull_slow, + + + pull_fail, + + + create_pod_slow, + + + create_pod_fail, + + + exec_fail, + + + others_fail, + + + eva_fail_ratio, + + + eva_timeout, + + + eva_timeout_ratio, + + + + + #{id,jdbcType=BIGINT}, + + + #{statDate,jdbcType=INTEGER}, + + + #{evaTotal,jdbcType=INTEGER}, + + + #{pullSlow,jdbcType=INTEGER}, + + + #{pullFail,jdbcType=INTEGER}, + + + #{createPodSlow,jdbcType=INTEGER}, + + + #{createPodFail,jdbcType=INTEGER}, + + + #{execFail,jdbcType=INTEGER}, + + + #{othersFail,jdbcType=INTEGER}, + + + #{evaFailRatio,jdbcType=DOUBLE}, + + + #{evaTimeout,jdbcType=INTEGER}, + + + #{evaTimeoutRatio,jdbcType=DOUBLE}, + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/OjEvaDayStatMapper.xml b/common/target/classes/mybatis/OjEvaDayStatMapper.xml new file mode 100644 index 0000000..a368924 --- /dev/null +++ b/common/target/classes/mybatis/OjEvaDayStatMapper.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + id, stat_date, eva_total, eva_fail, eva_fail_ratio, eva_timeout, eva_timeout_ratio + + + insert into oj_eva_day_stat + + + id, + + + stat_date, + + + eva_total, + + + eva_fail, + + + eva_fail_ratio, + + + eva_timeout, + + + eva_timeout_ratio, + + + + + #{id,jdbcType=BIGINT}, + + + #{statDate,jdbcType=INTEGER}, + + + #{evaTotal,jdbcType=INTEGER}, + + + #{evaFail,jdbcType=INTEGER}, + + + #{evaFailRatio,jdbcType=DOUBLE}, + + + #{evaTimeout,jdbcType=INTEGER}, + + + #{evaTimeoutRatio,jdbcType=DOUBLE}, + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/PlatformConfigMapper.xml b/common/target/classes/mybatis/PlatformConfigMapper.xml new file mode 100644 index 0000000..ba0bfcd --- /dev/null +++ b/common/target/classes/mybatis/PlatformConfigMapper.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + id, platform, containers, script, file_name_suffix, create_time, update_time + + + + insert into platform_config + + + id, + + + platform, + + + containers, + + + create_time, + + + update_time, + + + script, + + + file_name_suffix, + + + + + #{id,jdbcType=BIGINT}, + + + #{platform,jdbcType=VARCHAR}, + + + #{containers,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + #{script,jdbcType=LONGVARCHAR}, + + + #{fileNameSuffix,jdbcType=VARCHAR}, + + + + + + update platform_config + + + platform = #{platform,jdbcType=VARCHAR}, + + + containers = #{containers,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + script = #{script,jdbcType=LONGVARCHAR}, + + + file_name_suffix = #{fileNameSuffix,jdbcType=VARCHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/RunPodMapper.xml b/common/target/classes/mybatis/RunPodMapper.xml new file mode 100644 index 0000000..f6d5e4d --- /dev/null +++ b/common/target/classes/mybatis/RunPodMapper.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, cluster, name, image_name, + cpu_limit, memory_limit, cpu_request, memory_request, + create_by, node_ip, + run_pod_type, svc_port, ssh_port, + priority, + expire_time, create_time, update_time + + + + + delete from + run_pod + where name = #{name,jdbcType=VARCHAR} + + + delete from + run_pod + where name = #{name,jdbcType=VARCHAR} and expire_time <= #{expireTime,jdbcType=TIMESTAMP} + + + + insert into run_pod (id,cluster, name, + image_name, + cpu_limit, + memory_limit, + cpu_request, + memory_request, + create_by, + node_ip, + run_pod_type, + priority, + svc_port, + ssh_port, + expire_time, + create_time, + update_time) + values + (#{id,jdbcType=BIGINT}, + #{cluster,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR}, + #{imageName,jdbcType=VARCHAR}, + #{cpuLimit,jdbcType=DOUBLE}, + #{memoryLimit,jdbcType=INTEGER}, + #{cpuRequest,jdbcType=DOUBLE}, + #{memoryRequest,jdbcType=INTEGER}, + #{createBy,jdbcType=VARCHAR}, + #{nodeIp,jdbcType=VARCHAR}, + #{runPodType,jdbcType=INTEGER}, + #{priority,jdbcType=BIGINT}, + #{svcPort,jdbcType=VARCHAR}, + #{sshPort,jdbcType=VARCHAR}, + #{expireTime,jdbcType=TIMESTAMP}, + #{createTime,jdbcType=TIMESTAMP}, + #{updateTime,jdbcType=TIMESTAMP}) + + + insert into run_pod + + + id, + + + cluster, + + + name, + + + image_name, + + + cpu_limit, + + + memory_limit, + + + cpu_request, + + + memory_request, + + + create_by, + + + node_ip, + + + run_pod_type, + + + priority, + + + svc_port, + + + ssh_port, + + + expire_time, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{cluster,jdbcType=VARCHAR}, + + + #{name,jdbcType=VARCHAR}, + + + #{imageName,jdbcType=VARCHAR}, + + + #{cpuLimit,jdbcType=DOUBLE}, + + + #{memoryLimit,jdbcType=INTEGER}, + + + #{cpuRequest,jdbcType=DOUBLE}, + + + #{memoryRequest,jdbcType=INTEGER}, + + + #{createBy,jdbcType=VARCHAR}, + + + #{nodeIp,jdbcType=VARCHAR}, + + + #{runPodType,jdbcType=INTEGER}, + + + #{priority,jdbcType=BIGINT}, + + + #{svcPort,jdbcType=VARCHAR}, + + + #{sshPort,jdbcType=VARCHAR}, + + + #{expireTime,jdbcType=TIMESTAMP}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update run_pod + + + name = #{name,jdbcType=VARCHAR}, + + + cluster = #{cluster,jdbcType=VARCHAR}, + + + image_name = #{imageName,jdbcType=VARCHAR}, + + + cpu_limit = #{cpuLimit,jdbcType=DOUBLE}, + + + memory_limit = #{memoryLimit,jdbcType=INTEGER}, + + + cpu_request = #{cpuRequest,jdbcType=DOUBLE}, + + + memory_request = #{memoryRequest,jdbcType=INTEGER}, + + + create_by = #{createBy,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + run_pod_type = #{runPodType,jdbcType=INTEGER}, + + + priority = #{priority,jdbcType=BIGINT}, + + + svc_port = #{svcPort,jdbcType=VARCHAR}, + + + ssh_port = #{sshPort,jdbcType=VARCHAR}, + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + + update run_pod + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where name = #{name,jdbcType=VARCHAR} + + + + update run_pod set expire_time = #{expireTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where name = #{name,jdbcType=VARCHAR} and expire_time < #{expireTime,jdbcType=TIMESTAMP} + + + + + update run_pod + set name = #{name,jdbcType=VARCHAR}, + cluster = #{cluster,jdbcType=VARCHAR}, + image_name = + #{imageName,jdbcType=VARCHAR}, + cpu_limit = + #{cpuLimit,jdbcType=DOUBLE}, + memory_limit = + #{memoryLimit,jdbcType=INTEGER}, + cpu_request = + #{cpuRequest,jdbcType=DOUBLE}, + memory_request = + #{memoryRequest,jdbcType=INTEGER}, + create_by = + #{createBy,jdbcType=VARCHAR}, + node_ip = + #{nodeIp,jdbcType=VARCHAR}, + run_pod_type = + #{runPodType,jdbcType=INTEGER}, + priority = #{priority,jdbcType=BIGINT}, + svc_port = #{svcPort,jdbcType=VARCHAR}, + ssh_port = + #{sshPort,jdbcType=VARCHAR}, + expire_time = + #{expireTime,jdbcType=TIMESTAMP}, + create_time + =#{createTime,jdbcType=TIMESTAMP}, + update_time + =#{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + + + + + + + + + UPDATE + run_pod + + + svc_port = #{svcPort,jdbcType=VARCHAR}, + + + ssh_port = #{sshPort,jdbcType=VARCHAR}, + + + node_ip = #{nodeIp,jdbcType=VARCHAR}, + + + WHERE + name = #{name,jdbcType=VARCHAR} + + + UPDATE + run_pod + + + expire_time = #{expireTime,jdbcType=VARCHAR}, + + + WHERE + cluster = #{cluster,jdbcType=VARCHAR} + and node_ip = #{nodeIp,jdbcType=VARCHAR} + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SecurityContextConfigMapper.xml b/common/target/classes/mybatis/SecurityContextConfigMapper.xml new file mode 100644 index 0000000..e4db894 --- /dev/null +++ b/common/target/classes/mybatis/SecurityContextConfigMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + id, cluster, image_name, privileged, add_cap, drop_cap, create_time, update_time + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysConfigMapper.xml b/common/target/classes/mybatis/SysConfigMapper.xml new file mode 100644 index 0000000..2f7d37c --- /dev/null +++ b/common/target/classes/mybatis/SysConfigMapper.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + id, name, val, expire_in, expire_time, create_time, + update_time, status + + + + + insert into sys_config (id, name, val, + expire_in, + expire_time, create_time, + update_time, status + values + (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, + #{val,jdbcType=VARCHAR}, + #{expireIn,jdbcType=INTEGER}, + #{expireTime,jdbcType=TIMESTAMP}, #{createTime,jdbcType=TIMESTAMP}, + #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=CHAR}) + + + insert into sys_config + + + id, + + + name, + + + val, + + + expire_in, + + + expire_time, + + + create_time, + + + update_time, + + + status, + + + + + #{id,jdbcType=BIGINT}, + + + #{name,jdbcType=VARCHAR}, + + + #{val,jdbcType=VARCHAR}, + + + #{expireIn,jdbcType=INTEGER}, + + + #{expireTime,jdbcType=TIMESTAMP}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + #{status,jdbcType=CHAR}, + + + + + update sys_config + + + name = #{name,jdbcType=VARCHAR}, + + + val = #{val,jdbcType=VARCHAR}, + + + expire_in = #{expireIn,jdbcType=INTEGER}, + + + expire_time = #{expireTime,jdbcType=TIMESTAMP}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + status = #{status,jdbcType=CHAR}, + + + where id = #{id,jdbcType=BIGINT} + + + update sys_config + set name = #{name,jdbcType=VARCHAR}, + val = #{val,jdbcType=VARCHAR}, + expire_in = + #{expireIn,jdbcType=INTEGER}, + expire_time = + #{expireTime,jdbcType=TIMESTAMP}, + create_time = + #{createTime,jdbcType=TIMESTAMP}, + update_time = + #{updateTime,jdbcType=TIMESTAMP}, + status = #{status,jdbcType=CHAR} + where id = #{id,jdbcType=BIGINT} + + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysMenuMapper.xml b/common/target/classes/mybatis/SysMenuMapper.xml new file mode 100644 index 0000000..5209a4e --- /dev/null +++ b/common/target/classes/mybatis/SysMenuMapper.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysRoleMapper.xml b/common/target/classes/mybatis/SysRoleMapper.xml new file mode 100644 index 0000000..3d40def --- /dev/null +++ b/common/target/classes/mybatis/SysRoleMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysRoleMenuMapper.xml b/common/target/classes/mybatis/SysRoleMenuMapper.xml new file mode 100644 index 0000000..f45f534 --- /dev/null +++ b/common/target/classes/mybatis/SysRoleMenuMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + delete from edu_sys_role_menu where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysTokenMapper.xml b/common/target/classes/mybatis/SysTokenMapper.xml new file mode 100644 index 0000000..c15b2c7 --- /dev/null +++ b/common/target/classes/mybatis/SysTokenMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysUserMapper.xml b/common/target/classes/mybatis/SysUserMapper.xml new file mode 100644 index 0000000..7acc16b --- /dev/null +++ b/common/target/classes/mybatis/SysUserMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/SysUserRoleMapper.xml b/common/target/classes/mybatis/SysUserRoleMapper.xml new file mode 100644 index 0000000..4cc5213 --- /dev/null +++ b/common/target/classes/mybatis/SysUserRoleMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + delete from edu_sys_user_role where role_id in + + #{roleId} + + + + + + + + + + \ No newline at end of file diff --git a/common/target/classes/mybatis/WindowsInfoMapper.xml b/common/target/classes/mybatis/WindowsInfoMapper.xml new file mode 100644 index 0000000..f9f6068 --- /dev/null +++ b/common/target/classes/mybatis/WindowsInfoMapper.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + id, uniq_id, instance_id, user_id, port, vnc_port, template_name, auto_release_time, forward_table_id, forward_entry_id, vnc_forward_entry_id, status + + + + + + + + + + + + + update windows_info set status = -1 + where uniq_id = #{uniqId,jdbcType=VARCHAR} + + + + insert into windows_info + + + id, + + + uniq_id, + + + instance_id, + + + user_id, + + + port, + + + vnc_port, + + + template_name, + + + auto_release_time, + + + forward_table_id, + + + forward_entry_id, + + + vnc_forward_entry_id, + + + create_time, + + + update_time, + + + + + #{id,jdbcType=BIGINT}, + + + #{uniqId,jdbcType=VARCHAR}, + + + #{instanceId,jdbcType=VARCHAR}, + + + #{userID,jdbcType=VARCHAR}, + + + #{port,jdbcType=VARCHAR}, + + + #{vncPort,jdbcType=VARCHAR}, + + + #{templateName,jdbcType=VARCHAR}, + + + #{autoReleaseTime,jdbcType=TIMESTAMP}, + + + #{forwardTableId,jdbcType=VARCHAR}, + + + #{forwardEntryId,jdbcType=VARCHAR}, + + + #{vncForwardEntryId,jdbcType=VARCHAR}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + update windows_info + + + instance_id = #{instanceId,jdbcType=VARCHAR}, + + + user_id = #{userID,jdbcType=VARCHAR}, + + + port = #{port,jdbcType=INTEGER}, + + + vnc_port = #{vncPort,jdbcType=INTEGER}, + + + template_name = #{templateName,jdbcType=VARCHAR}, + + + auto_release_time = #{autoReleaseTime,jdbcType=TIMESTAMP}, + + + forward_table_id = #{forwardTableId,jdbcType=VARCHAR}, + + + forward_entry_id = #{forwardEntryId,jdbcType=VARCHAR}, + + + vnc_forward_entry_id = #{vncForwardEntryId,jdbcType=VARCHAR}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where uniq_id = #{uniqId,jdbcType=VARCHAR} + + \ No newline at end of file diff --git a/parent/parent.iml b/parent/parent.iml new file mode 100644 index 0000000..30b9e61 --- /dev/null +++ b/parent/parent.iml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml new file mode 100644 index 0000000..7dc95d9 --- /dev/null +++ b/parent/pom.xml @@ -0,0 +1,311 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.6 + + + com.imitate + parent + 0.0.1-SNAPSHOT + parent + parent + pom + + + + + 1.8 + 1.2.83 + 4.4.5 + 2.0.1.Final + + 2.1.0 + + 1.2.10 + + 1.1.10 + + 5.7.11 + 3.12.0 + 1.9.0 + 2.9.0 + 2.7 + 3.6 + 1.9 + 5.11.2 + 2.9.0 + 1.9.0.RELEASE + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-aop + + + + com.spring4all + swagger-spring-boot-starter + ${swagger2.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.projectlombok + lombok + + + + com.alibaba + fastjson + ${fastjson.version} + + + + org.apache.httpcomponents + httpcore + ${httpcore.version} + + + + + javax.validation + validation-api + ${javax.validation.version} + + + org.springframework.boot + spring-boot-starter-validation + + + + org.apache.commons + commons-lang3 + ${commons-lang3-version} + + + + + + cn.hutool + hutool-all + ${hutool.version} + compile + + + + tk.mybatis + mapper-spring-boot-starter + ${mapper.version} + + + + mybatis-spring-boot-starter + org.mybatis.spring.boot + + + log4j-api + org.apache.logging.log4j + + + + + + + mysql + mysql-connector-java + runtime + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.version} + + + + + + org.apache.shiro + shiro-core + ${shiro.version} + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + com.google.code.gson + gson + ${gson.version} + + + + + + commons-io + commons-io + ${commons-io.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + commons-net + commons-net + ${commons-net.version} + + + org.apache.commons + commons-text + ${commons-text.version} + + + io.fabric8 + kubernetes-client + ${kubernetes-client.version} + + + redis.clients + jedis + ${jedis.version} + + + + com.google.guava + guava + 22.0 + + + + com.googlecode.aviator + aviator + 5.1.2 + + + + com.aliyun + aliyun-java-sdk-core + 4.4.6 + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.1.0 + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.imitate.web.WebApplication + execute + + + + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + false + true + 1.8 + ${java.version} + ${java.version} + false + UTF8 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + src/main/java + + + **/*.xml + **/*.properties + + false + + + src/main/resources + + **/*.py + **/*.sh + **/*.xml + **/*.properties + + false + + + + + + + imitate-${project.version} + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a0d0bf8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + com.imitate + imitate-system + 0.0.1-SNAPSHOT + imitate-system + imitate-system + pom + + + parent + common + web + + + diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 0000000..bbb0c5f --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + parent + com.imitate + 0.0.1-SNAPSHOT + ../parent/pom.xml + + + web + 0.0.1-SNAPSHOT + web + web + jar + + + + + com.imitate + common + 0.0.1-SNAPSHOT + + + + + + diff --git a/web/src/main/java/com/imitate/web/WebApplication.java b/web/src/main/java/com/imitate/web/WebApplication.java new file mode 100644 index 0000000..6fa6303 --- /dev/null +++ b/web/src/main/java/com/imitate/web/WebApplication.java @@ -0,0 +1,24 @@ +package com.imitate.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScans; +import tk.mybatis.spring.annotation.MapperScan; + + + +@MapperScan(basePackages = {"com.imitate.web.persistence.mapper", + "com.imitate.common.shiro.mapper", + "com.imitate.common.sys.mapper", + "com.imitate.common.k8s.mapper" +}) +@ComponentScans(value = {@ComponentScan("com.imitate.common")}) +@SpringBootApplication +public class WebApplication { + public static void main(String[] args) { + SpringApplication.run(WebApplication.class, args); + } +} + + diff --git a/web/src/main/java/com/imitate/web/aspect/ParamsOutAspect.java b/web/src/main/java/com/imitate/web/aspect/ParamsOutAspect.java new file mode 100644 index 0000000..51406c5 --- /dev/null +++ b/web/src/main/java/com/imitate/web/aspect/ParamsOutAspect.java @@ -0,0 +1,166 @@ +package com.imitate.web.aspect; + +import com.alibaba.fastjson.JSON; +import com.imitate.common.util.HttpContextUtils; +import com.imitate.common.util.IpUtil; +import com.imitate.web.persistence.beans.SysLog; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 拦截controller,输出入参、响应内容和响应时间 + * + * @author 威少 + */ +@Component +@Aspect +@Slf4j +public class ParamsOutAspect implements Ordered { + + + @Autowired + private ApplicationEventPublisher publisher; + + + @Pointcut("execution(* com.imitate.web.module.*.*.*Controller.*(..))") + public void controllerLogPointCut() { + } + + + @AfterThrowing(throwing="ex",pointcut = "controllerLogPointCut()") + public void doRecoveryActions(JoinPoint joinPoint, Throwable ex) throws ClassNotFoundException { + SysLog sysLog = new SysLog(); + Instant startTime = Instant.now(); + Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); + RequestMapping methodMapping = method.getAnnotation(RequestMapping.class); + if (methodMapping != null) { + sysLog.setMethodPath(methodMapping.path()[0]); + if (methodMapping.method().length > 0) { + sysLog.setHttpType(methodMapping.method()[0].name()); + } else { + sysLog.setHttpType("GET"); + } + } + + ApiOperation methodApiOperation = method.getAnnotation(ApiOperation.class); + if (methodApiOperation != null) { + sysLog.setOperation(methodApiOperation.value()); + } + + //请求的方法名 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + sysLog.setMethod(className + "." + methodName + "()"); + + Class clazz = Class.forName(className); + RequestMapping clazzReqMapping = clazz.getAnnotation(RequestMapping.class); + if (clazzReqMapping != null && clazzReqMapping.value().length > 0) { + sysLog.setClassPath(clazzReqMapping.value()[0]); + } + Api clazzApi = clazz.getAnnotation(Api.class); + if (clazzApi != null) { + sysLog.setModuleName(clazzApi.value()); + } + + String[] params = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); + Object[] args = joinPoint.getArgs(); + + //取入参 + Map paramsMap = new HashMap<>(); + if (params != null) { + for (int i = 0; i < params.length; i++) { + if ((!(args[i] instanceof HttpServletRequest) && !(args[i] instanceof HttpServletResponse)) && !(args[i] instanceof MultipartFile)) { + paramsMap.put(params[i], args[i]); + } + } + } + sysLog.setParams(JSON.toJSONString(paramsMap)); + + //记录耗时 + int cost = Duration.between(startTime, Instant.now()).getNano() / 1000000; + sysLog.setTime(cost); + sysLog.setCreateTime(LocalDateTime.now()); + sysLog.setUpdateTime(LocalDateTime.now()); + //获取request + HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); + //设置IP地址 + String ip = IpUtil.getIpAddr(request); + if ("0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) { + sysLog.setIp("localhost"); + } else { + sysLog.setIp(ip); + } + //设置返回结果 + sysLog.setResult(getStackTraceInfo(ex)); + + String remoteHost = request.getRemoteHost(); + if (!"127.0.0.1".equals(remoteHost) && !"系统健康检测".equals(sysLog.getOperation())) { + //异步发消息写redis + + } + + } + + + /** + * 获取e.printStackTrace() 的具体信息,赋值给String 变量,并返回 + * @param e + * Throwable + * @return e.printStackTrace() 中 的信息 + */ + public static String getStackTraceInfo(Throwable e) { + StringWriter sw = null; + PrintWriter pw = null; + try { + sw = new StringWriter(); + pw = new PrintWriter(sw); + e.printStackTrace(pw);//将出错的栈信息输出到printWriter中 + pw.flush(); + sw.flush(); + return sw.toString(); + } catch (Exception ex) { + return "printStackTrace()转换错误"; + } finally { + if (sw != null) { + try { + sw.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + if (pw != null) { + pw.close(); + } + } + } + + @Override + public int getOrder() { + return 1; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/example/controller/UserController.java b/web/src/main/java/com/imitate/web/module/example/controller/UserController.java new file mode 100644 index 0000000..43c2f60 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/example/controller/UserController.java @@ -0,0 +1,96 @@ +package com.imitate.web.module.example.controller; + + +import com.imitate.common.annotation.PublicUrl; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.test.Person; +import com.imitate.common.util.R; +import com.imitate.common.util.BasicController; +import com.imitate.web.params.DemoParam; +import com.imitate.web.vo.WindowsInfoDelegate; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.annotations.Param; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; + + +@Slf4j +@RestController +@Api(value = "game控制器", hidden = true) +@RequestMapping("/user") +public class UserController extends BasicController { + + + + @Autowired + private Person defaultStudent; + + @Autowired + @Qualifier("jdbcTemplate") + private JdbcTemplate jdbcTemplate; + + + /** + * GET请求带分页搜索-示例 + * @param windowsInfoDelegate + * @return + */ + @RequestMapping(path = "/test",method = RequestMethod.GET) + public R getPerson(WindowsInfoDelegate windowsInfoDelegate){ + return R.ok().setData(windowsInfoDelegate); + } + + + + /** + * POST请求 含表单验证提交, 参数绑定异常含中文提示 + * @param demoParam + * @return + */ + @PublicUrl(signValidate = true) + @RequestMapping(path = "/abc",method = RequestMethod.POST) + public R getPerson2(@RequestBody @Valid DemoParam demoParam){ + log.info("demoParam:{}",demoParam); + /*if(bindingResult.hasErrors()){ + return actionResultWithBindingResult(ErrorCodeEnum.BIND_EXCEPTION,bindingResult); + }*/ + return R.ok().setData(demoParam); + } + + //@PublicUrl(signValidate = true) + @ApiOperation(value = "开启实训", httpMethod = "POST") + @RequestMapping(path = "/open",method = RequestMethod.POST) + public R getPerson3(@ApiParam(name = "tpmGitURL", required = true, value = "Tpm的gitUrl,需要base64编码") @RequestParam String tpmGitURL) throws Exception{ + Map resultMap = new HashMap<>(0); + try{ + String sql = "select (select concat('https://www.educoder.net/tasks/',t4.identifier) from games t4 where 1=1 and t4.myshixun_id = t1.id order by t4.updated_at desc limit 0,1) as tpiUrl,t1.identifier as tpiIdentifier,concat('https://www.educoder.net/users/',t2.login) as user_domain,t2.nickname,t3.name,concat('https://www.educoder.net/shixuns/',t3.identifier,'/challenges') as shixun_url from myshixuns t1 left join users t2 on t1.user_id = t2.id left join shixuns t3 on t1.shixun_id = t3.id where 1=1 and t1.id = 18 limit 0,1"; + resultMap = jdbcTemplate.queryForMap(sql); + if(resultMap.size() > 0){ + System.out.println(resultMap); + } + }catch (EmptyResultDataAccessException e){ + e.printStackTrace(); + } + return R.ok(resultMap); + } + + + + + + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/comparator/ClusterInfoCmp.java b/web/src/main/java/com/imitate/web/module/game/comparator/ClusterInfoCmp.java new file mode 100644 index 0000000..94d1372 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/comparator/ClusterInfoCmp.java @@ -0,0 +1,105 @@ +package com.imitate.web.module.game.comparator; + +import com.imitate.common.k8s.bean.ClusterInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Comparator; + +/** + * 集群比较器 + * + * @author mumu + * + */ +public class ClusterInfoCmp implements Comparator { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final static ClusterInfoCmp instance = new ClusterInfoCmp(); + + private ClusterInfoCmp() { + super(); + } + + public static ClusterInfoCmp getInstance() { + return instance; + } + + @Override + public int compare(ClusterInfo o1, ClusterInfo o2) { + + // 是否可扩容的比较 + boolean r = Boolean.TRUE.equals(o1.getAutoscale()); + boolean r2 = Boolean.TRUE.equals(o2.getAutoscale()); + if (r && (!r2)) { + return -1; + } else if ((!r) && r2) { + return 1; + } + + // 按照weight 选则扩容节点 + double o1ExpectWeightRatio = calRatio(o1.getWeight(), o2.getWeight()); + + // 按照 cpu 分配 + double o1ActualRequestCpuRatio = calRatio(o1.getRequestCpuSum() + o1.getReserveRequestCpuSum(), + o2.getRequestCpuSum() + o2.getReserveRequestCpuSum()); + double surplusCpuDiff = o1ExpectWeightRatio - o1ActualRequestCpuRatio; + logger.debug("ClusterInfoCmp o1ExpectWeightRatio: {}, o1ActualRequestCpuRatio: {}, surplusCpuDiff: {}", + o1ExpectWeightRatio, o1ActualRequestCpuRatio, surplusCpuDiff); + if(surplusCpuDiff >= 0.05) { + return -1; + } else if(surplusCpuDiff <= -0.05) { + return 1; + } + + // 按照内存分配 + double o1ActualRequestMemoryRatio = calRatio(o1.getRequestMemorySum() + o1.getReserveRequestMemorySum(), + o2.getRequestMemorySum() + o2.getReserveRequestMemorySum()); + double surplusMemoryDiff = o1ExpectWeightRatio - o1ActualRequestMemoryRatio; + if(surplusMemoryDiff >= 0.05) { + return -1; + } else if(surplusMemoryDiff <= -0.05) { + return 1; + } + + // 1.绝对值,2.先cpu、后内存 + if (Math.abs(surplusCpuDiff) > Math.abs(surplusMemoryDiff)) { + if (surplusCpuDiff > 0) { + return -1; + } else if (surplusCpuDiff <= 0) { + return 1; + } + } else { + if (surplusMemoryDiff > 0) { + return -1; + } else if (surplusMemoryDiff <= 0) { + return 1; + } + } + + if(o1.getWeight() > o2.getWeight()) { + return -1; + } else if(o1.getWeight() < o2.getWeight()) { + return 1; + } + + return 0; + } + + /** + * 计算第一个加数占总和的比例 + * + * @param addend + * 所有加数 + * @return + */ + private double calRatio(double... addend) { + double sum = 0; + for (double anAddend : addend) { + sum += anAddend; + } + return addend[0] / sum; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/config/PoolConfig.java b/web/src/main/java/com/imitate/web/module/game/config/PoolConfig.java new file mode 100644 index 0000000..870ebc0 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/config/PoolConfig.java @@ -0,0 +1,61 @@ +package com.imitate.web.module.game.config; + +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.web.module.game.thread.BridgeThreadPoolTaskExecutor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +public class PoolConfig { + @Autowired + private AppConfig appConfig; + + @Bean("threadPoolTaskExecutor") + public BridgeThreadPoolTaskExecutor tpPool() { + + BridgeThreadPoolTaskExecutor executor = new BridgeThreadPoolTaskExecutor(); + executor.setCorePoolSize(appConfig.getEvaMaxPoolSize() / 2); + executor.setMaxPoolSize(appConfig.getEvaMaxPoolSize()); + executor.setQueueCapacity(appConfig.getEvaMaxPoolSize()); + executor.setWaitForTasksToCompleteOnShutdown(true); + + return executor; + } + + @Bean("gitTaskExecutor") + public ThreadPoolTaskExecutor clonePool() { + + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(appConfig.getEvaMaxPoolSize() / 2); + executor.setMaxPoolSize(appConfig.getEvaMaxPoolSize()); + executor.setQueueCapacity(appConfig.getEvaMaxPoolSize()); + executor.setWaitForTasksToCompleteOnShutdown(true); + + return executor; + } + + + @Bean("logExecutor") + public ThreadPoolTaskExecutor logExecutor(){ + ThreadPoolTaskExecutor logExecutor = new ThreadPoolTaskExecutor(); + //核心线程数 + logExecutor.setCorePoolSize(200); + //最大线程数 + logExecutor.setMaxPoolSize(500); + //队列最大长度 + logExecutor.setQueueCapacity(1000); + //线程池维护线程所允许的空闲时间 + logExecutor.setKeepAliveSeconds(300); + //配置拒绝策略 + logExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + //如果不初始化,导致找到不到执行器 + logExecutor.initialize(); + return logExecutor; + } + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/controller/GameController.java b/web/src/main/java/com/imitate/web/module/game/controller/GameController.java new file mode 100644 index 0000000..a1d3870 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/controller/GameController.java @@ -0,0 +1,220 @@ +package com.imitate.web.module.game.controller; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.annotation.PublicUrl; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.enums.CommonStateEnum; +import com.imitate.common.k8s.pojo.BridgePod; +import com.imitate.common.k8s.service.BridgePodService; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.service.PortService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.*; +import com.imitate.web.module.game.service.ClusterScheduleMgr; +import com.imitate.web.module.game.service.GameService; +import com.imitate.web.module.game.thread.eva.TpEvaJob; +import com.imitate.web.params.GameEvaluateParam; +import com.imitate.web.vo.GameEvaluateVO; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import static com.imitate.common.constant.TpCsts.*; + +/** + * 和实训相关的接口 + * + * @author weishao + */ +@RestController +@RequestMapping("/game") +public class GameController extends BasicController { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private AppConfig appConfig; + + @Autowired + private GameService gameService; + + @Autowired + private PortService portService; + + @Autowired + private BridgePodService bridgePodService; + + + @Qualifier("threadPoolTaskExecutor") + @Autowired + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Autowired + private ClusterScheduleMgr clusterScheduleMgr; + + @Autowired + private RunPodService runPodService; + + @Autowired + private DiskService diskService; + + + @PublicUrl(signValidate = true) + @RequestMapping(path = "/gameEvaluate") + public R gameEvaluate(@Valid @RequestBody GameEvaluateParam reqVO) { + logger.info("[start]评测:{}", JSONObject.toJSONString(reqVO)); + // 记录开始时间 + LocalDateTime requestTime = LocalDateTime.now(); + + String tpiID = reqVO.getTpiID(); + // 参数处理 + Integer timeLimit = reqVO.getTimeLimit() == null ? Integer.parseInt(appConfig.getDefaultTimeLimit()) : reqVO.getTimeLimit(); + Integer needPortMapping = reqVO.getNeedPortMapping() == null ? 0 : reqVO.getNeedPortMapping(); + Long priority = reqVO.getPriority() == null ? 0 : reqVO.getPriority(); + // 1(webssh)类型的实训在评测时使用的是0(evaluate)类型 + Integer podType = reqVO.getPodType() == 1 ? 0 : reqVO.getPodType(); + String podName = TpUtils.buildEvaPodName(reqVO.getTpiID(), podType); + String containers = Base64Util.decode(reqVO.getContainers()); + // 主类别容器 + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + String tpiGitURL = Base64Util.decode(reqVO.getTpiGitURL()); + String testCases = StringUtil.replaceLineSeparator(Base64Util.decode(reqVO.getTestCases())); + String tpiWorkspace = diskService.getTpiWorkspaceHostPath(reqVO.getTpiID()); + String tpiRepoName = TpCsts.TP_UNIFY_REPO_NAME; + String tpiRepoPath = diskService.buildTpiRepoPath(tpiWorkspace, tpiRepoName); + JSONObject buildParams = setBuildParams(tpiID, reqVO.getTpID(), priority, tpiGitURL, reqVO.getBuildID(), reqVO.getIsPublished(), reqVO.getInstanceChallenge(), testCases, + reqVO.getTrimBlank(), reqVO.getTestCasesType(), reqVO.getTestCasesExp(), reqVO.getTpmScript(), timeLimit, reqVO.getExecuteTime(), reqVO.getResubmit(), needPortMapping, podType, containers, + reqVO.getContentModified(), reqVO.getSecKey(), reqVO.getSecretGitUrl(), reqVO.getSecretDir(), reqVO.getCallBackUrl(), reqVO.getSimpleCallBackUrl(), reqVO.getExtras(), reqVO.getBigDataFile(), + requestTime, mainContainer, tpiWorkspace, tpiRepoName, tpiRepoPath, reqVO.getTpmIdentifier(), reqVO.getRunOnly()); + + // 若需要端口映射服务,则分配端口 + Integer port = -1; + if (needPortMapping != -1 && needPortMapping != 0) { + port = runPodService.getServicePort(podName); + if (port == null) { + port = portService.allocatePort(SysConfigCsts.KEY_USABLE_PORT_K8S); + logger.info("评测分配端口 tpiID: {}, port: {}", reqVO.getTpID(), port); + } else { + logger.info("评测使用旧端口 tpiID: {}, port: {}", reqVO.getTpiID(), port); + } + buildParams.put("nodePort", port); + } + + // 若实训生成文件 + String file = reqVO.getFile(); + if (!StringUtils.isEmpty(file)) { + file = Base64Util.decode(file); + buildParams.put("file", file); + // 清空目标文件夹以防止影响此次评测结果 + GameHelper.clearFiles(tpiRepoPath + File.separator + JSONObject.parseObject(file).getString("path")); + } + + // pod 是否需要建立在本地节点 + if (podType == TpCsts.POD_TYPE_VNC || (needPortMapping != -1 && needPortMapping != 0) + || StringUtils.isNotEmpty(file)) { + buildParams.put("localNode", Boolean.TRUE); + } else { + buildParams.put("localNode", Boolean.FALSE); + } + + // 重复评测检测 + gameService.redoEvaluatingHandle(tpiID, timeLimit); + + // 创建历史Pod请求记录 + BridgePod bridgePod = bridgePodService.createBridgePod(podName, tpiID, reqVO.getBuildID(), requestTime, reqVO.getSecKey()); + buildParams.put("bridgePodId", bridgePod.getId()); + + // 获取集群 + String cluster = runPodService.getRunPodCluster(tpiID, podType); + if (StringUtils.isEmpty(cluster)) { + cluster = clusterScheduleMgr.chooseCluster(tpiID, podType, containers); + } + buildParams.put("cluster", cluster); + + // 执行评测 + TpEvaJob tpEvaJob = gameService.getTpEvaJob(buildParams); + threadPoolTaskExecutor.execute(tpEvaJob); + + ZoneId zone = ZoneId.systemDefault(); + + GameEvaluateVO respVO = new GameEvaluateVO(); + respVO.setPort(port); + respVO.setWaitNum(0); + respVO.setCostTime(Duration.between(requestTime.atZone(zone).toInstant(), Instant.now()).toMillis()); + respVO.setAbleToCreate(1); + + logger.info("[end]评测:tpiID: {}, buildID: {}", tpiID, reqVO.getBuildID()); + return R.ok().setData(respVO); + } + + private JSONObject setBuildParams(String tpiID, String tpID, Long priority, String tpiGitURL, String buildID, Integer isPublished, String instanceChallenge, + String testCases, Integer trimBlank, Integer testCasesType, String testCasesExp, String tpmScript, Integer timeLimit, Integer executeTime, + String resubmit, Integer needPortMapping, Integer podType, String containers, Integer content_modified, String sec_key, String secretGitUrl, + String secretDir, String callBackUrl, String simpleCallBackUrl, String extras, String bigDataFile, LocalDateTime requestTime, JSONObject mainContainer, + String tpiWorkspace, String tpiRepoName, String tpiRepoPath, String tpmIdentifier, Integer runOnly) { + JSONObject buildParams = new JSONObject(true); + buildParams.put("tpiID", tpiID); + buildParams.put("tpID", tpID); + buildParams.put("priority", priority); + buildParams.put("mainContainer", mainContainer); + buildParams.put("tpiWorkspace", tpiWorkspace); + buildParams.put("tpiGitURL", tpiGitURL); + buildParams.put("buildID", buildID); + buildParams.put("isPublished", isPublished); + buildParams.put("instanceChallenge", instanceChallenge); + buildParams.put("testCases", testCases); + buildParams.put("trimBlank", trimBlank); + buildParams.put("testCasesType", testCasesType); + buildParams.put("testCasesExp", testCasesExp); + buildParams.put("timeLimit", timeLimit); + buildParams.put("resubmit", resubmit); + buildParams.put("tpmScript", tpmScript); + buildParams.put("needPortMapping", needPortMapping); + buildParams.put("podType", podType); + buildParams.put("containers", containers); + buildParams.put("contentModified", content_modified); + buildParams.put("executeTime", executeTime); + buildParams.put("sec_key", sec_key); + buildParams.put("ojFastEva", Boolean.FALSE); + buildParams.put("evaluateStartTime", requestTime.toString()); + buildParams.put("tpiRepoName", tpiRepoName); + buildParams.put("tpiRepoPath", tpiRepoPath); + buildParams.put("extras", extras); + buildParams.put("bigDataFile", bigDataFile); + buildParams.put("tpmIdentifier", tpmIdentifier); + buildParams.put("runOnly", runOnly); + if (CommonStateEnum.fromInteger(runOnly) == CommonStateEnum.TRUE) { + buildParams.put("callBackUrl", IGNORED); + buildParams.put("simpleCallBackUrl", IGNORED); + JedisUtil.psetex(RUN_ONLY_RESULT_KEY_PREFIX + tpiID, CASE_OUTPUT_SEPARATOR, (long) (timeLimit + 1) * 1000); + } else { + buildParams.put("callBackUrl", callBackUrl); + buildParams.put("simpleCallBackUrl", simpleCallBackUrl); + } + if (StringUtils.isNotEmpty(secretDir)) { + buildParams.put("secretDir", secretDir); + String secretRepospace = diskService.buildSecretRepoSpace(tpiRepoPath, secretDir); + buildParams.put("secretRepospace", secretRepospace); + secretGitUrl = Base64Util.decode(secretGitUrl); + buildParams.put("secretGitUrl", secretGitUrl); + } + return buildParams; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/controller/VscodeController.java b/web/src/main/java/com/imitate/web/module/game/controller/VscodeController.java new file mode 100644 index 0000000..f41be12 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/controller/VscodeController.java @@ -0,0 +1,185 @@ +package com.imitate.web.module.game.controller; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.annotation.PublicUrl; +import com.imitate.common.bean.ApiResult; +import com.imitate.common.constant.ApiResultCsts; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.Base64Util; +import com.imitate.common.util.R; +import com.imitate.common.util.ThreadUtils; +import com.imitate.web.module.game.service.GameService; +import com.imitate.web.module.game.service.GitService; +import com.imitate.web.module.game.service.VscodeService; +import com.imitate.web.module.game.thread.code.LocalCheckJob; +import com.imitate.web.params.GetVsCodeParam; +import com.imitate.web.params.VsCodeDeleteParam; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * @author yanchao + * @date 2021/12/17 + */ +@RestController +@RequestMapping("/vscode") +public class VscodeController { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private AppConfig appConfig; + + @Autowired + private VscodeService vscodeService; + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private GitService gitService; + + @Autowired + private GameService gameService; + + @Autowired + private DiskService diskService; + + @Qualifier("gitTaskExecutor") + @Autowired + private ThreadPoolTaskExecutor gitTaskExecutor; + + @PublicUrl(signValidate = true) + @RequestMapping(path = "/getVscode",method = RequestMethod.POST) + public R getVscode(@Valid @RequestBody GetVsCodeParam vsCodeParam) throws Exception { + + logger.info("[start]获取vscode连接信息 {}", JSONObject.toJSONString(vsCodeParam)); + JSONObject resp = new JSONObject(); + + //若容器为数组 + if ("W10=".equals(vsCodeParam.getContainers())) { + return R.error("-3", "容器为空数组"); + + } + + String containers = Base64Util.decode(vsCodeParam.getContainers()); + + //若不是正确镜像则给出提示 目前支持java/c/python环境 + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + String imageName = mainContainer.getString("imageName"); + if (!CollectionUtil.toList("vscode-ww", "gcc-codeserver-ww", "python3d7-codeserver").contains(imageName)) { + return R.error("-3", "错误的镜像名称:" + imageName); + } + + + boolean createImage = vsCodeParam.getCreateImage() != null && vsCodeParam.getCreateImage(); + + //指定默认集群 + String cluster = "local"; + String tpiID = vsCodeParam.getTpiID(); + String tpiGitURL = vsCodeParam.getTpiGitURL(); + // 检查本地版本库 + if (StringUtils.isNotEmpty(tpiGitURL)) { + if (clusterManager.isLocalCluster(cluster)) { + String tpiWorkspace = diskService.getTpiWorkspaceHostPath(tpiID); + boolean localCodeExist = gameService.checkLocalCodeExist(tpiID, tpiGitURL, tpiWorkspace); + if (!localCodeExist) { + // 下载本地版本库 + LocalCheckJob localCheckJob = gameService.getLocalCheckJob(tpiID, tpiGitURL, tpiWorkspace); + gitTaskExecutor.execute(localCheckJob); + } + } else { + gitTaskExecutor.execute(new Runnable() { + @Override + public void run() { + gitService.remoteClusterCheckCode(cluster, Boolean.TRUE, tpiID, tpiGitURL); + } + }); + } + } + + //返回vscode路径地址 + String targetUrl = vscodeService.getVscode(cluster, tpiID, vsCodeParam.getPodType(), containers, vsCodeParam.getBigDataFile(), vsCodeParam.getSurvivalSecond(), createImage, vsCodeParam.getNeedPortMapping()); + + // 环境存在问题则重新获取创建一次 + if (targetUrl == null) { + targetUrl = vscodeService.getVscode(cluster, tpiID, vsCodeParam.getPodType(), containers, vsCodeParam.getBigDataFile(), vsCodeParam.getSurvivalSecond(), createImage, vsCodeParam.getNeedPortMapping()); + } + + + if (targetUrl == null) { + return R.error("-2", "当前实验使用的用户较多,系统正在智能化为您调度更优质的资源,预计一分钟内完成,请稍后重试!"); + } else { + Map respData = new HashMap<>(2); + respData.put("url", targetUrl); + return R.ok(respData); + } + } + + + @PublicUrl(signValidate = true) + @PostMapping(path = "/delete") + public R delete(@Valid @RequestBody VsCodeDeleteParam vsCodeDeleteParam) { + + String tpiID = vsCodeDeleteParam.getTpiID(); + logger.info("[start]前端主动调用vscode删除命令,tpiID: {}", tpiID); + + String tpiRepoName = TpCsts.TP_UNIFY_REPO_NAME; + String tpiWorkspace = diskService.getTpiWorkspaceHostPath(tpiID); + String tpiRepoPath = diskService.buildTpiRepoPath(tpiWorkspace, tpiRepoName); + + // 用identifier与私钥拼接与digestKey比较,相等响应,不想等拒绝 + String serverDigestKey = DigestUtils.sha1Hex(vsCodeDeleteParam.getIdentifier() + appConfig.getSecretKey()); + if (vsCodeDeleteParam.getDigestKey().equals(serverDigestKey)) { + try { + vscodeService.deleteNow(tpiID); + + // 删除本地版本库 + FileUtils.deleteDirectory(new File(tpiWorkspace)); + ThreadUtils.sleep(2000); + + + } catch (Exception e) { + logger.error("主动删除vscode pod {} 失败 {}", tpiID, e); + return R.error("-1", "删除pod失败"); + } + + } else { + logger.error("主动删除vscode pod{}失败,认证不通过", tpiID); + return R.error("-1", "认证不通过"); + } + + logger.info("[end]前端主动调用vscode删除命令,tpiID: {}", tpiID); + return R.ok(); + } + + + @PublicUrl(signValidate = true) + @RequestMapping(path = "/active/{tpiID}", method = RequestMethod.POST) + public R active(@PathVariable("tpiID") String tpiID) { + logger.info("[start]激活vscode pod {},延长到期时间", tpiID); + + vscodeService.active(tpiID); + + logger.info("[end]激活vscode pod {},延长到期时间", tpiID); + return R.ok(); + } + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/enums/GitUrlComponent.java b/web/src/main/java/com/imitate/web/module/game/enums/GitUrlComponent.java new file mode 100644 index 0000000..c3afd36 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/enums/GitUrlComponent.java @@ -0,0 +1,35 @@ +package com.imitate.web.module.game.enums; + +/** + * git地址组成 + * + * @author 威少 + */ +public enum GitUrlComponent { + /** + * 协议 + */ + PROTOCOL(0), + /** + * HOST + */ + HOST(1), + /** + * 路径 + */ + PATH(2); + + GitUrlComponent(int value) { + this.value = value; + } + + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/EvaErrorResUsage.java b/web/src/main/java/com/imitate/web/module/game/pojo/EvaErrorResUsage.java new file mode 100644 index 0000000..67eda3a --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/EvaErrorResUsage.java @@ -0,0 +1,101 @@ +package com.imitate.web.module.game.pojo; + +/** + * 评测错误资源使用情况 + */ +public class EvaErrorResUsage { + + private Integer exitStatus; + + private long evaCpuUsage; + + private long evaMaxMemUsage; + + private String nodeLoadAvg; + + private Integer nodeCpuUsageRate; + + private double nodeLoadAvgOneMinute; + + private double nodeLoadAvgFiveMinute; + + private double nodeLoadAvgFifteenMinute; + + public Integer getExitStatus() { + return exitStatus; + } + + public void setExitStatus(Integer exitStatus) { + this.exitStatus = exitStatus; + } + + public long getEvaCpuUsage() { + return evaCpuUsage; + } + + public void setEvaCpuUsage(long evaCpuUsage) { + this.evaCpuUsage = evaCpuUsage; + } + + public long getEvaMaxMemUsage() { + return evaMaxMemUsage; + } + + public void setEvaMaxMemUsage(long evaMaxMemUsage) { + this.evaMaxMemUsage = evaMaxMemUsage; + } + + public Integer getNodeCpuUsageRate() { + return nodeCpuUsageRate; + } + + public void setNodeCpuUsageRate(Integer nodeCpuUsageRate) { + this.nodeCpuUsageRate = nodeCpuUsageRate; + } + + public double getNodeLoadAvgOneMinute() { + return nodeLoadAvgOneMinute; + } + + public void setNodeLoadAvgOneMinute(double nodeLoadAvgOneMinute) { + this.nodeLoadAvgOneMinute = nodeLoadAvgOneMinute; + } + + public double getNodeLoadAvgFiveMinute() { + return nodeLoadAvgFiveMinute; + } + + public void setNodeLoadAvgFiveMinute(double nodeLoadAvgFiveMinute) { + this.nodeLoadAvgFiveMinute = nodeLoadAvgFiveMinute; + } + + public double getNodeLoadAvgFifteenMinute() { + return nodeLoadAvgFifteenMinute; + } + + public void setNodeLoadAvgFifteenMinute(double nodeLoadAvgFifteenMinute) { + this.nodeLoadAvgFifteenMinute = nodeLoadAvgFifteenMinute; + } + + public String getNodeLoadAvg() { + return nodeLoadAvg; + } + + public void setNodeLoadAvg(String nodeLoadAvg) { + this.nodeLoadAvg = nodeLoadAvg; + } + + @Override + public String toString() { + return "EvaErrorResUsage{" + + "exitStatus=" + exitStatus + + ", evaCpuUsage=" + evaCpuUsage + + ", evaMaxMemUsage=" + evaMaxMemUsage + + ", nodeLoadAvg='" + nodeLoadAvg + '\'' + + ", nodeCpuUsageRate=" + nodeCpuUsageRate + + ", nodeLoadAvgOneMinute=" + nodeLoadAvgOneMinute + + ", nodeLoadAvgFiveMinute=" + nodeLoadAvgFiveMinute + + ", nodeLoadAvgFifteenMinute=" + nodeLoadAvgFifteenMinute + + '}'; + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/EvaStepOut.java b/web/src/main/java/com/imitate/web/module/game/pojo/EvaStepOut.java new file mode 100644 index 0000000..96048f9 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/EvaStepOut.java @@ -0,0 +1,27 @@ +package com.imitate.web.module.game.pojo; + +/** + * 评测分布输出 + */ +public class EvaStepOut { + + private String step; + + private Integer ratio; + + public String getStep() { + return step; + } + + public void setStep(String step) { + this.step = step; + } + + public Integer getRatio() { + return ratio; + } + + public void setRatio(Integer ratio) { + this.ratio = ratio; + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/ExecSourceCase.java b/web/src/main/java/com/imitate/web/module/game/pojo/ExecSourceCase.java new file mode 100644 index 0000000..96206d9 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/ExecSourceCase.java @@ -0,0 +1,33 @@ +package com.imitate.web.module.game.pojo; + +/** + * 执行的源测试用例,对应输入及预期输出 + * + * @author 威少 + */ +public class ExecSourceCase { + public enum Type { + /** + * 文本 + */ + TEXT("0"), + /** + * 文件 + */ + FILE("1"); + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + Type(String value) { + this.value = value; + } + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/GitPullRequestParam.java b/web/src/main/java/com/imitate/web/module/game/pojo/GitPullRequestParam.java new file mode 100644 index 0000000..e289b4c --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/GitPullRequestParam.java @@ -0,0 +1,108 @@ +package com.imitate.web.module.game.pojo; + + +/** + * git信息 + */ +public class GitPullRequestParam { + + private String tpiId; + + private String tpiGitURL; + + private String tpiWorkspace; + + private String tpiRepoName; + + private String tpiProtectspace; + + private String tpmScript; + + private String secretDir; + + private String secretGitUrl; + + private Integer contentModified; + + private String file; + + public String getTpiId() { + return tpiId; + } + + public void setTpiId(String tpiId) { + this.tpiId = tpiId; + } + + public String getTpiGitURL() { + return tpiGitURL; + } + + public void setTpiGitURL(String tpiGitURL) { + this.tpiGitURL = tpiGitURL; + } + + public String getTpiWorkspace() { + return tpiWorkspace; + } + + public void setTpiWorkspace(String tpiWorkspace) { + this.tpiWorkspace = tpiWorkspace; + } + + public String getTpiRepoName() { + return tpiRepoName; + } + + public void setTpiRepoName(String tpiRepoName) { + this.tpiRepoName = tpiRepoName; + } + + public String getTpiProtectspace() { + return tpiProtectspace; + } + + public void setTpiProtectspace(String tpiProtectspace) { + this.tpiProtectspace = tpiProtectspace; + } + + public String getTpmScript() { + return tpmScript; + } + + public void setTpmScript(String tpmScript) { + this.tpmScript = tpmScript; + } + + public String getSecretDir() { + return secretDir; + } + + public void setSecretDir(String secretDir) { + this.secretDir = secretDir; + } + + public String getSecretGitUrl() { + return secretGitUrl; + } + + public void setSecretGitUrl(String secretGitUrl) { + this.secretGitUrl = secretGitUrl; + } + + public Integer getContentModified() { + return contentModified; + } + + public void setContentModified(Integer contentModified) { + this.contentModified = contentModified; + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/GitResetRequestParam.java b/web/src/main/java/com/imitate/web/module/game/pojo/GitResetRequestParam.java new file mode 100644 index 0000000..ef6fc1c --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/GitResetRequestParam.java @@ -0,0 +1,68 @@ +package com.imitate.web.module.game.pojo; + + +/** + * git信息 + */ +public class GitResetRequestParam { + + private String tpiId; + + private String tpiGitURL; + + private String tpmGitURL; + + private String tpiWorkspace; + + private String tpiRepoName; + + private String tpiRepoPath; + + public String getTpiId() { + return tpiId; + } + + public void setTpiId(String tpiId) { + this.tpiId = tpiId; + } + + public String getTpiGitURL() { + return tpiGitURL; + } + + public void setTpiGitURL(String tpiGitURL) { + this.tpiGitURL = tpiGitURL; + } + + public String getTpmGitURL() { + return tpmGitURL; + } + + public void setTpmGitURL(String tpmGitURL) { + this.tpmGitURL = tpmGitURL; + } + + public String getTpiWorkspace() { + return tpiWorkspace; + } + + public void setTpiWorkspace(String tpiWorkspace) { + this.tpiWorkspace = tpiWorkspace; + } + + public String getTpiRepoName() { + return tpiRepoName; + } + + public void setTpiRepoName(String tpiRepoName) { + this.tpiRepoName = tpiRepoName; + } + + public String getTpiRepoPath() { + return tpiRepoPath; + } + + public void setTpiRepoPath(String tpiRepoPath) { + this.tpiRepoPath = tpiRepoPath; + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/LocalCheck.java b/web/src/main/java/com/imitate/web/module/game/pojo/LocalCheck.java new file mode 100644 index 0000000..ba3fa89 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/LocalCheck.java @@ -0,0 +1,40 @@ +package com.imitate.web.module.game.pojo; + +public class LocalCheck { + + private String tpiID; + private String tpiGitURL; + private String tpiWorkSpace; + + public LocalCheck(String tpiID, String tpiGitURL, String tpiWorkSpace) { + super(); + this.tpiID = tpiID; + this.tpiGitURL = tpiGitURL; + this.tpiWorkSpace = tpiWorkSpace; + } + + public String getTpiID() { + return tpiID; + } + + public void setTpiID(String tpiID) { + this.tpiID = tpiID; + } + + public String getTpiGitURL() { + return tpiGitURL; + } + + public void setTpiGitURL(String tpiGitURL) { + this.tpiGitURL = tpiGitURL; + } + + public String getTpiWorkSpace() { + return tpiWorkSpace; + } + + public void setTpiWorkSpace(String tpiWorkSpace) { + this.tpiWorkSpace = tpiWorkSpace; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/ResUsage.java b/web/src/main/java/com/imitate/web/module/game/pojo/ResUsage.java new file mode 100644 index 0000000..472faed --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/ResUsage.java @@ -0,0 +1,48 @@ +package com.imitate.web.module.game.pojo; + +import java.util.List; + +public class ResUsage { + + private List testSetUsages; + private Long runMaxMem; + + public List getTestSetUsages() { + return testSetUsages; + } + + public void setTestSetUsages(List testSetUsages) { + this.testSetUsages = testSetUsages; + } + + public Long getRunMaxMem() { + return runMaxMem; + } + + public void setRunMaxMem(Long runMaxMem) { + this.runMaxMem = runMaxMem; + } + + public static class TestSetUsage { + private Long testSetTime; + private Long testSetMem; + + public Long getTestSetTime() { + return testSetTime; + } + + public void setTestSetTime(Long testSetTime) { + this.testSetTime = testSetTime; + } + + public Long getTestSetMem() { + return testSetMem; + } + + public void setTestSetMem(Long testSetMem) { + this.testSetMem = testSetMem; + } + + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/SimpleBuildResult.java b/web/src/main/java/com/imitate/web/module/game/pojo/SimpleBuildResult.java new file mode 100644 index 0000000..d43f079 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/SimpleBuildResult.java @@ -0,0 +1,54 @@ +package com.imitate.web.module.game.pojo; + +/** + * 简单评测结果 + * + * @author jzx + */ + +public class SimpleBuildResult { + private String buildID; + private Integer status; + + public static final int STATUS_MSG = 0; + + public static final int STATUS_REDO_EVA = 1; + + public static final int STATUS_STEP_OUT = 2; + + private String textMsg; + private Integer ratio; + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Integer getRatio() { + return ratio; + } + + public void setRatio(Integer ratio) { + this.ratio = ratio; + } + + public String getBuildID() { + return buildID; + } + + public void setBuildID(String buildID) { + this.buildID = buildID; + } + + public String getTextMsg() { + return textMsg; + } + + public void setTextMsg(String textMsg) { + this.textMsg = textMsg; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/pojo/TimeoutResult.java b/web/src/main/java/com/imitate/web/module/game/pojo/TimeoutResult.java new file mode 100644 index 0000000..e7ff750 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/pojo/TimeoutResult.java @@ -0,0 +1,31 @@ +package com.imitate.web.module.game.pojo; + +public class TimeoutResult { + + private Integer timeoutCode; + + private String out; + + public TimeoutResult(Integer timeoutCode, String out) { + super(); + this.timeoutCode = timeoutCode; + this.out = out; + } + + public Integer getTimeoutCode() { + return timeoutCode; + } + + public void setTimeoutCode(Integer timeoutCode) { + this.timeoutCode = timeoutCode; + } + + public String getOut() { + return out; + } + + public void setOut(String out) { + this.out = out; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/ClusterScheduleMgr.java b/web/src/main/java/com/imitate/web/module/game/service/ClusterScheduleMgr.java new file mode 100644 index 0000000..b5f2a76 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/ClusterScheduleMgr.java @@ -0,0 +1,266 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.BridgeContainer; +import com.imitate.common.k8s.bean.ClusterInfo; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.ThreadUtils; +import com.imitate.common.util.TpUtils; +import com.imitate.web.module.game.comparator.ClusterInfoCmp; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 集群管理 + */ +@Component +public class ClusterScheduleMgr { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private RunPodService runPodService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private AppConfig appConfig; + + public String getCluster(String tpiID, Integer podType, String containers) { + String cluster = runPodService.getRunPodCluster(tpiID, podType); + if (StringUtils.isNotEmpty(cluster)) { + // pod已经存在,直接返回cluster + statefulPodScheduleRecord(cluster, tpiID, podType); + return cluster; + } + + String key = TpCsts.POD_SCHEDULE_CLUSTER_REDIS_KEY + tpiID; + cluster = JedisUtil.get(key); + if (StringUtils.isNotEmpty(cluster)) { + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + BridgeContainer bc = ContainerUtil.parseBridgeContainer(mainContainer.toJSONString()); + if (clusterManager.canSupportedImageName(cluster, bc.getName())) { + statefulPodScheduleRecord(cluster, tpiID, podType); + return cluster; + } + } + + return StringUtils.isNotEmpty(cluster) ? cluster : chooseCluster(tpiID, podType, containers); + } + + private String getClusterForRedis(String tpiID) { + String cluster = null; + int times = 10; + while (times-- > 0) { + String key = TpCsts.POD_SCHEDULE_CLUSTER_REDIS_KEY + tpiID; + cluster = JedisUtil.get(key); + if (StringUtils.isNotEmpty(cluster)) { + break; + } + ThreadUtils.sleep(300); + } + return cluster; + } + + /** + * 当前没有中心调度节点控制,集群规模扩大后,总是集中分配到某一个集群!!! + * + * @param tpiId + * @param podType + * @param containers + * @return + */ + public String chooseCluster(String tpiId, Integer podType, String containers) { + String podName = TpUtils.buildEvaPodName(tpiId, podType); + + // 新分配 + JSONObject mainContainer = ContainerUtil.getMainContainer(containers); + BridgeContainer bc = ContainerUtil.parseBridgeContainer(mainContainer.toJSONString()); + String cluster = assignCluster(tpiId, podType, podName, bc); + clusterManager.clusterReserveRequestRes(cluster, bc); + + statefulPodScheduleRecord(cluster, tpiId, podType); + return cluster; + } + + private String assignCluster(String tpiId, Integer podType, String podName, BridgeContainer bc) { + // 2.排除不支持镜像的集群,得到候选集群 + List candidates = clusterManager.getSupportedClusterInfo(bc.getName()); + if (candidates.isEmpty()) { + throw new RuntimeException("不支持的实验环境:" + bc.getName()); + } + + if (candidates.size() == 1) { + return candidates.get(0).getClusterConfig().getName(); + } + + boolean statefulSchedulePodFlag = TpUtils.isStatefulPod(podType); + + // 对于vnc, webssh, evassh, 在2天(与清理workspace的时间相同)内,有调度记录的,调度到同一个集群 + // 评测类pod,如果存在相应的webssh pod记录,要发布到同一个集群 + String alreadyCluster = JedisUtil.get(TpCsts.STATEFUL_POD_CLUSTER_RECORD_REDIS_KEY + tpiId); + if (clusterManager.canSupportedImageName(alreadyCluster, bc.getName()) + && canScheduleAlReadyCluster(statefulSchedulePodFlag, alreadyCluster, candidates)) { + return alreadyCluster; + } + + // 3.分配集群 + // 分配优先级:3.1 本地集群固定节点,3.2 远程集群固定节点,3.3 本地、远程扩容节点 + + // 3.1 本地集群固定节点 + List cInfoList = getClusterInfo(candidates, Boolean.TRUE); + for (ClusterInfo cInfo : cInfoList) { + if (isForeverNodeRich(statefulSchedulePodFlag, cInfo)) { + return cInfo.getClusterConfig().getName(); + } + } + + // 3.2 远程集群固定节点 + cInfoList = getClusterInfo(candidates, Boolean.FALSE); + for (ClusterInfo cInfo : cInfoList) { + if (isForeverNodeRich(statefulSchedulePodFlag, cInfo)) { + return cInfo.getClusterConfig().getName(); + } + } + + // 3.3 本地、远程扩容节点 + List autoscales = getAutoscale(candidates); + if (autoscales.size() > 0) { + candidates = autoscales; + } + + // 排序 + Collections.sort(candidates, ClusterInfoCmp.getInstance()); + + String cluster = candidates.get(0).getClusterConfig().getName(); + logger.debug("选择集群信息排序 tpiID: {}, cluster: {}", tpiId, candidates.get(0)); + return cluster; + } + + private boolean canScheduleAlReadyCluster(boolean statefulSchedulePodFlag, String alreadyCluster, List candidates) { + if (StringUtils.isNotEmpty(alreadyCluster)) { + for (ClusterInfo clusterInfo : candidates) { + if (alreadyCluster.equals(clusterInfo.getClusterConfig().getName())) { + // 集群可扩展 + if (clusterInfo.getAutoscale()) { + logger.debug("canScheduleAlReadyCluster 集群可扩展, cluster: {}", alreadyCluster); + return Boolean.TRUE; + } + + // 不可扩展集群,固定节点资源有余额 + if (isForeverNodeRich(statefulSchedulePodFlag, clusterInfo)) { + logger.debug("canScheduleAlReadyCluster 不可扩展集群,固定节点资源有余额, cluster: {}", alreadyCluster); + return Boolean.TRUE; + } + + // 其它集群不可扩展,资源也不足 + if (!canScheduleOtherCluster(statefulSchedulePodFlag, alreadyCluster, candidates)) { + logger.debug("canScheduleAlReadyCluster 其它集群不可扩展,资源也不足, cluster: {}", alreadyCluster); + return Boolean.TRUE; + } + } + } + } + return Boolean.FALSE; + } + + private boolean canScheduleOtherCluster(boolean statefulSchedulePodFlag, String alreadyCluster, List candidates) { + for (ClusterInfo clusterInfo : candidates) { + if (alreadyCluster.equals(clusterInfo.getClusterConfig().getName())) { + continue; + } + // 集群可扩展 + if (clusterInfo.getAutoscale()) { + return Boolean.TRUE; + } + // 不可扩展集群,固定节点资源有余额 + if (isForeverNodeRich(statefulSchedulePodFlag, clusterInfo)) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + private List getClusterInfo(List cis, Boolean local) { + List list = new ArrayList<>(); + for (ClusterInfo cInfo : cis) { + if (local.equals(cInfo.getClusterConfig().getLocal())) { + list.add(cInfo); + } + } + return list; // TODO 可加上排序, 如简单按照权重排序 + } + + private List getAutoscale(List candidates) { + List list = new ArrayList<>(); + for (ClusterInfo cInfo : candidates) { + if (Boolean.TRUE.equals(cInfo.getClusterConfig().getAutoScale())) { + list.add(cInfo); + } + } + + return list; + } + + private boolean isForeverNodeRich(boolean statefulSchedulePodFlag, ClusterInfo cInfo) { + double requestCpuRatioLimit = 0; + double requestMemoryRatioLimit = 0; + if (statefulSchedulePodFlag) { + if (Boolean.TRUE.equals(cInfo.getClusterConfig().getAutoScale())) { + requestCpuRatioLimit = sysConfigService.getStatefulScaleClusterRequestCpuRatioLimit(); + requestMemoryRatioLimit = sysConfigService.getStatefulScaleClusterRequestMemoryRatioLimit(); + } else { + requestCpuRatioLimit = sysConfigService.getStatefulNotScaleClusterRequestCpuRatioLimit(); + requestMemoryRatioLimit = sysConfigService.getStatefulNotScaleClusterRequestMemoryRatioLimit(); + } + } else { + if (Boolean.TRUE.equals(cInfo.getClusterConfig().getAutoScale())) { + requestCpuRatioLimit = sysConfigService.getStatelessScaleClusterRequestCpuRatioLimit(); + requestMemoryRatioLimit = sysConfigService.getStatelessScaleClusterRequestMemoryRatioLimit(); + } else { + requestCpuRatioLimit = sysConfigService.getStatelessNotScaleClusterRequestCpuRatioLimit(); + requestMemoryRatioLimit = sysConfigService.getStatelessNotScaleClusterRequestMemoryRatioLimit(); + } + } + + double requestCpuRatio = (cInfo.getReserveRequestCpuSum() / cInfo.getForeverAllocatableCpuSum() * 100) + + cInfo.getConversionForeverNodeRequestCpuRatio(); + double requestMemoryRatio = (cInfo.getReserveRequestMemorySum() / cInfo.getForeverAllocatableMemorySum() * 100) + + cInfo.getConversionForeverNodeRequestMemoryRatio(); + logger.debug("isForeverNodeRich cluster: {}, requestCpuRatio: {}, requestCpuRatioLimit: {}, requestMemoryRatio: {}, requestMemoryRatioLimit: {}", + cInfo.getClusterConfig().getName(), requestCpuRatio, requestCpuRatioLimit, requestMemoryRatio, requestMemoryRatioLimit); + return requestCpuRatio < requestCpuRatioLimit && requestMemoryRatio < requestMemoryRatioLimit; + } + + /** + * 记录有状态pod集群分配信息 + * @param cluster + * @param tpiId + * @param podType + */ + private void statefulPodScheduleRecord(String cluster, String tpiId, Integer podType){ + if (TpUtils.isStatefulPod(podType)) { + logger.debug("statefulPodScheduleRecord cluster: {}, tpiId: {}, podType: {}", cluster, tpiId, podType); + JedisUtil.psetex(TpCsts.STATEFUL_POD_CLUSTER_RECORD_REDIS_KEY + tpiId, + cluster, TpCsts.STATEFUL_POD_CLUSTER_RECORD_EXPIRE_TIME); + } + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/EducoderService.java b/web/src/main/java/com/imitate/web/module/game/service/EducoderService.java new file mode 100644 index 0000000..480c94e --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/EducoderService.java @@ -0,0 +1,63 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.OKHttp3Utils3; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * Created by guange on 17/02/2017. + */ +@Service +public class EducoderService { + + private final static Logger logger = LoggerFactory.getLogger(EducoderService.class); + + @Autowired + private AppConfig appConfig; + + + public void commitResultToEducoder(String url, Map buildResult) { + JSONObject result = new JSONObject(buildResult); + if (StringUtils.isEmpty(url)) { + url = appConfig.getEducoderURL(); + } + if (!TpCsts.IGNORED.equals(url)) { + url += "?t=" + System.currentTimeMillis(); + logger.info("educoderURL: {}, result: {}", url, result); + OKHttp3Utils3.sendPost(url, result); + } + } + + public void commitOjResultToEducoder(String url, Map buildResult, String tpiID) { + JSONObject result = new JSONObject(buildResult); + if (StringUtils.isEmpty(url)) { + url = appConfig.getOjEvaCallBackURL().replace(":tpiid", tpiID); + } + if (!TpCsts.IGNORED.equals(url)) { + url += "?t=" + System.currentTimeMillis(); + logger.info("educoderURL: {}, result: {}", url, result); + OKHttp3Utils3.sendPost(url, result); + } + } + + public void commitSimpileResultToEducoder(String url, Map buildResult) { + JSONObject result = new JSONObject(buildResult); + if (StringUtils.isEmpty(url)) { + url = appConfig.getSimpileEducoderURL(); + } + if (!TpCsts.IGNORED.equals(url)) { + url += "?t=" + System.currentTimeMillis(); + logger.info("educoderURL: {}, result: {}", url, result); + OKHttp3Utils3.sendPost(url, result); + } + } + +} \ No newline at end of file diff --git a/web/src/main/java/com/imitate/web/module/game/service/EvaTimeoutService.java b/web/src/main/java/com/imitate/web/module/game/service/EvaTimeoutService.java new file mode 100644 index 0000000..5091a07 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/EvaTimeoutService.java @@ -0,0 +1,151 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.Splitter; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.service.NodeResUsageService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.GameHelper; +import com.imitate.common.util.JsonUtils; +import com.imitate.common.util.ProcessStatusUtil; +import com.imitate.web.module.game.pojo.EvaErrorResUsage; +import com.imitate.web.module.game.pojo.TimeoutResult; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.imitate.common.constant.TpCsts.CASE_OUTPUT_SEPARATOR; + +/** + * 评测超时服务 + */ +@Service +public class EvaTimeoutService { + + private static final Logger logger = LoggerFactory.getLogger(EvaTimeoutService.class); + + private static final int TIMEOUT_BLOCKING_MAX_CPU_USAGE = 2; + + @Autowired + private NodeResUsageService nodeResUsageService; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private DiskService diskService; + + public boolean isTimeout(int exitStatus, String out) { + if (ProcessStatusUtil.isTimeout(exitStatus) || outIsTimeout(out)) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + public TimeoutResult timeoutResultAnalysis(String cluster, String podName, String nodeName, String out, int casesSize, int timeLimit, int timeout, int isPublished, String tpiID) { + // 评测人数太多,当前实验环境压力过大,请稍后重试!该提示暂未加入提示中 + String hint = "代码评测超时!本关限定时间为:" + timeLimit + "s,程序可能存在以下问题:\n" + + "1. 代码存在阻塞操作,如读取输入阻塞、网络编程端口阻塞、多线程阻塞等。\n" + + "2. 若实训依赖网络下载,可能由于网络波动导致评测超时,请稍后重试!"; + if (isPublished != 1) { + hint += "\n3. 请评估评测代码执行时间,合理设置评测执行时间限制!"; + } + Integer timeoutCode = TpCsts.TIMEOUT_CODE_UNSURE; + + out = GameHelper.getTimeoutResUsage(out); + if (StringUtils.isNotEmpty(podName) && StringUtils.isNotEmpty(out)) { + EvaErrorResUsage timeoutResUsage = getEvaTimeoutResUsage(cluster, nodeName, out); + + Integer nodeCpuUsageRate = timeoutResUsage.getNodeCpuUsageRate(); + double seconds = timeoutResUsage.getEvaCpuUsage() / 1000000000.0d; + if (seconds >= timeout * 0.92) { + // 评测cpu消耗时间大于 timeLimit 则确定代码存在死循环 + hint = "代码评测超时!本关限定时间为:" + timeLimit + "s,请检查代码是否存在死循环"; + timeoutCode = TpCsts.TIMEOUT_CODE_DEAD_LOOP; + + } else if (seconds < TIMEOUT_BLOCKING_MAX_CPU_USAGE && nodeCpuUsageRate != TpCsts.NODE_CPU_USAGE_UNKNOWN + && nodeCpuUsageRate < sysConfigService.getNodeCpuMinUsageRate()) { + // 评测cpu消耗较小且节点cpu使用率小于节点cpu最小使用率则确定为io阻塞 + // hint = "代码评测超时!本关限定时间为:" + timeLimit + "s,请检查读取输入的代码!"; + timeoutCode = TpCsts.TIMEOUT_CODE_BLOCKING; + + } else if (nodeCpuUsageRate > sysConfigService.getNodeCpuMaxUsageRate()) { + // 节点cpu使用率超过节点cpu最大使用率则确定为系统繁忙 + // hint = "代码评测超时!本关限定时间为:" + timeLimit + "s,系统繁忙,请稍后再试!"; + timeoutCode = TpCsts.TIMEOUT_CODE_NODE_BUSY; + + } + logger.info("评测timeout, podName: {}, nodeName: {}, timeLimit: {}, timeout: {}, timeoutCode:{}, resource: {}", podName, nodeName, timeLimit, timeout, timeoutCode, timeoutResUsage); + } + List casesOutputList = getTimeoutTestCaseOutput(tpiID); + out = GameHelper.getTimeOutResult(casesSize, hint, casesOutputList); + return new TimeoutResult(timeoutCode, out); + } + + /** + * 从user.out用户输出文件提取超时场景下的输出 + * @param tpiID tpiID + * @return 用例输出 + */ + private List getTimeoutTestCaseOutput(String tpiID) { + try { + File file = new File(diskService.getTpiWorkspaceHostPath(tpiID) + File.separator + TpCsts.USER_OUTPUT_FILE); + String outputs = FileUtils.readFileToString(file); + List result = Splitter.on(CASE_OUTPUT_SEPARATOR).splitToList(outputs); + if (CollectionUtils.isNotEmpty(result)) { + return result.subList(0, result.size() - 1); + } else { + return new ArrayList<>(); + } + } catch (IOException e) { + return null; + } + } + + private boolean outIsTimeout(String out) { + return out.contains("\"exitStatus\":124") || out.contains("\"exitStatus\":137"); + } + + private EvaErrorResUsage getEvaTimeoutResUsage(String cluster, String nodeName, String out) { + EvaErrorResUsage resUsage = JsonUtils.toBean(out, EvaErrorResUsage.class); + + String loadAvgInfo = resUsage.getNodeLoadAvg(); + String[] loadAvgs = loadAvgInfo.split(" "); + resUsage.setNodeLoadAvgOneMinute(Double.parseDouble(loadAvgs[0])); + resUsage.setNodeLoadAvgFiveMinute(Double.parseDouble(loadAvgs[1])); + resUsage.setNodeLoadAvgFifteenMinute(Double.parseDouble(loadAvgs[2])); + + resUsage.setNodeCpuUsageRate(nodeResUsageService.getNodeCpuUsageRate(cluster, nodeName)); + return resUsage; + } + + public boolean isOverMemoryLimit(JSONObject buildParams, String oriOut) { + if (oriOut.contains("command terminated with exit code 137")) { + return true; + } + + JSONObject mainContainer = buildParams.getJSONObject("mainContainer"); + Integer memoryLimit = Integer.parseInt(mainContainer.getString("memoryLimit").replace("M", "")); + + String out = GameHelper.getTimeoutResUsage(oriOut); + if (StringUtils.isEmpty(out)) { + return false; + } + EvaErrorResUsage resUsage = JsonUtils.toBean(out, EvaErrorResUsage.class); + Long maxMemUsage = resUsage.getEvaMaxMemUsage(); + + logger.info("isOverMemoryLimit exitStatus: {}, memoryLimit: {} {}, maxMemUsage: {} {}", resUsage.getExitStatus(), + memoryLimit, memoryLimit * 0.92, maxMemUsage, maxMemUsage / 1024 / 1024); + return maxMemUsage / 1024 / 1024 > memoryLimit * 0.92 && 137 == resUsage.getExitStatus(); + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/ExecResultService.java b/web/src/main/java/com/imitate/web/module/game/service/ExecResultService.java new file mode 100644 index 0000000..4357e49 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/ExecResultService.java @@ -0,0 +1,66 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.k8s.bean.ExecResultCase; +import com.imitate.common.util.Base64Util; +import com.imitate.common.util.FileUtil; +import com.imitate.common.util.JedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.io.FileUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static com.imitate.common.constant.BuildResultCsts.COMPILE_SUCCESS_BASE64; +import static com.imitate.common.constant.TpCsts.RUN_ONLY_RESULT_KEY_PREFIX; + +/** + * @author 威少 + *

+ * 执行结果处理相关业务实现 + */ +@Service +@Slf4j +public class ExecResultService { + + + /** + * 处理文件类型case的输出 + * + * @param sourceCase 源case + * @param actualOutputPath 输出 + * @return case是否通过 + */ + public String dealWithFileTypeCase(JSONObject sourceCase, String actualOutputPath) { + String expectOutputPath = sourceCase.getString("output"); + String pass = ExecResultCase.Status.FAIL.getValue(); + try { + FileUtil.convertToUnixEOL(new File(expectOutputPath)); + pass = FileUtils.contentEquals(new File(expectOutputPath), new File(actualOutputPath)) ? ExecResultCase.Status.PASS.getValue() : pass; + } catch (IOException e) { + log.error("read file case actual output failed.", e); + } + return pass; + } + + /** + * 对仅运行的评测运行结束之后写结果到redis + * + * @param tpiID tpiId + * @param compileResult 编译结果,base64编码 + * @param outputs 执行结果,base64编码的列表 + */ + public void saveRunOnlyResultToRedis(String tpiID, String compileResult, List outputs) { + String result = ""; + if (!COMPILE_SUCCESS_BASE64.equals(compileResult)) { + result += Base64Util.decode(compileResult); + } else if (CollectionUtils.isNotEmpty(outputs)) { + result += Base64Util.decode(outputs.get(0)); + } + + JedisUtil.psetex(RUN_ONLY_RESULT_KEY_PREFIX + tpiID, result, 500L); + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/GameService.java b/web/src/main/java/com/imitate/web/module/game/service/GameService.java new file mode 100644 index 0000000..5bfd685 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/GameService.java @@ -0,0 +1,1469 @@ +package com.imitate.web.module.game.service; + +import cn.hutool.core.codec.Base64; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.bean.ApiResult; +import com.imitate.common.bean.BeanFactory; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.constant.BuildResultCsts; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.enums.CommonStateEnum; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.k8s.bean.BuildResult; +import com.imitate.common.k8s.bean.ExecResultCase; +import com.imitate.common.k8s.bean.TimeCost; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.BridgePodService; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.ResourceFileService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.*; +import com.imitate.web.module.game.enums.GitUrlComponent; +import com.imitate.web.module.game.pojo.*; +import com.imitate.web.module.game.thread.code.LocalCheckJob; +import com.imitate.web.module.game.thread.eva.TpEvaJob; +import com.imitate.web.module.game.util.AviatorUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * created by weishao at 2017/8/1 + */ +@Service("GameService") +public class GameService { + @Autowired + private EducoderService educoderService; + @Autowired + private AppConfig appConfig; + @Autowired + private BridgePodService bridgePodService; + + @Autowired + ResourceFileService resourceFileService; + + @Autowired + private GitService gitService; + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private ClusterManager clusterManager; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private GitAccountService gitAccountService; + + @Autowired + private ExecResultService execResultService; + + @Autowired + private DiskService diskService; + + private static final Logger logger = LoggerFactory.getLogger(GameService.class); + + /** + * 开启实训时第0步构建--克隆版本库 + * + * @param tpiWorkSpace + * 根据TpiID拼接出来的文件路径 ${workspace}/myshixun_${TpiID} + * @param tpmGitURL + * Tpm版本库地址 + * @param remoteName + * 版本库远端名称 + * @param tpiRepoName + * Tpi版本库名称,需要这个参数的原因是,TPM的仓库名字和TPI的名字并不总是相同的 + */ + public void gitClone(String tpiWorkSpace, String tpmGitURL, String remoteName, String tpiRepoName, String depth) + throws BusinessException { + // 如果没有工作目录,先创建 + File file = FileUtils.getFile(tpiWorkSpace); + if (!file.exists()) { + try { + FileUtils.forceMkdir(file); + } catch (IOException e) { + logger.error("克隆代码时,创建实训工作目录{}失败", tpiWorkSpace, e); + throw new BusinessException(ErrorCodeEnum.CLONE_FAIL.getValue(),"克隆代码时,创建实训工作目录" + tpiWorkSpace + "失败"); + } + } + + // 如果TPI版本库已经存在 + if (FileUtils.getFile(tpiWorkSpace, tpiRepoName).exists()) { + logger.info("版本库已经存在!tpiWorkSpace: {}", tpiWorkSpace); + return; + } + // 执行克隆操作 + String cmd = "cd " + tpiWorkSpace + " && git clone -o " + remoteName + " " + depth + " " + tpmGitURL + " " + tpiRepoName + " && cd " + + tpiRepoName + " && git config user.email educoder@163.com && git config user.name educoder"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 3); + if (result.getExitStatus() != 0) { + if (result.getOut().contains("already exists")) { + logger.warn("tpiWorkSpace:{} 共享磁盘同步延迟,未及时检测到文件已经存在!e: {}, cmd: {}", tpiWorkSpace, result, cmd); + throw new BusinessException("0", "共享磁盘同步延迟,tpiWorkSpace:" + tpiWorkSpace); // 如果是评测,稍后pull + } + if (result.getOut().contains("Username for")) { + logger.info("git credential缺失!重新设置并克隆, cmd: {}, result: {}", cmd, result); + gitAccountService.gitCredentialStore(gitService.getGitUrlComponent(tpmGitURL, GitUrlComponent.HOST), gitService.getGitUrlComponent(tpmGitURL, GitUrlComponent.PROTOCOL)); + gitClone(tpiWorkSpace, tpmGitURL, remoteName, tpiRepoName, depth); + return; + } + logger.error("tpmGitURL:{} 克隆失败!e: {}, cmd: {}", tpmGitURL, result, cmd); + String msg = "克隆失败,版本库地址:" + tpmGitURL; + msg = result.getOut().contains("not found") ? (msg + ",reason:not found.") : msg; + throw new BusinessException(ErrorCodeEnum.CLONE_FAIL.getValue(), msg); + } + logger.info("克隆成功!tpiWorkSpace: {}", tpiWorkSpace); + + } + + /** + * 如果存在在树莓派执行脚本文件包,将文件包拷贝到/data/workspace/userfiles目录 + * @param tpiWorkSpace + */ + public void copyToUserFile(String tpiWorkSpace, String tpiID) { + try { + String raspberryScript = tpiWorkSpace + File.separator + TpCsts.TP_UNIFY_REPO_NAME + File.separator + TpCsts.TP_RASPBERRY_REPO_SCRIPT; + + String cmd = "ls " + raspberryScript; + ShellResult shellResult = ShellUtil.executeAndGetExitStatus(cmd); + + if (FileUtils.getFile(raspberryScript).exists()) { + String newDir = tpiWorkSpace + File.separator + TpCsts.TP_USERFILES_NAME; + if(FileUtils.getFile(newDir).exists()){ + FileUtils.deleteDirectory(new File(newDir)); + } + FileUtils.forceMkdir(new File(newDir)); + FileUtils.copyDirectory(new File(raspberryScript), new File(newDir)); + //替换脚本中的占位符为 tpiID + List fileList = (List) FileUtils.listFiles(new File(raspberryScript), new String[]{"py"}, true); + FileUtils.copyDirectory(new File(raspberryScript), new File(tpiWorkSpace + File.separator + TpCsts.TP_USERFILES_NAME)); + fileList.stream().forEach(file -> modifyFileContent(file, "TOPIC", tpiID)); + //生成执行命令脚本保存到文件 + String context = "export tpiID=" + tpiID +" && echo " + tpiID + " > ~/.educoder.cfg && wget -O userfiles.zip https://data.educoder.net/api/myshixuns/download_file.json?tpiID=" + tpiID + " && unzip -d userfiles/ userfiles.zip && cd userfiles/ && bash run.sh \n"; + FileUtils.writeStringToFile(new File(newDir + File.separator + "INSTALL.TXT"),context); + + File file = FileUtils.getFile(newDir + File.separator + "INSTALL.TXT"); + + FileReader reader = new FileReader(file); + int temp = 0; + while ((temp = reader.read()) != -1){ + logger.info("{}",(char)temp); + } + + } + }catch (IOException e) { + e.printStackTrace(); + logger.error("移动文件夹时:" + e.toString()); + } + } + + + public boolean cloudHostGitClone(String tpiID, String cloudHostConn, String tpiWorkSpace, String tpmGitURL, String tpiRepoName, String codeWorkspace) { + // 执行克隆操作 + String cmd = cloudHostConn + "'rm -rf "+ codeWorkspace +" && cd " + tpiWorkSpace + " && git clone -o origin " + tpmGitURL + " " + tpiRepoName + " && cd " + + tpiRepoName + " && git config user.email educoder@163.com && git config user.name educoder'"; + logger.info("开始远程克隆!cmd: {}, tpiId: {}", cmd, tpiID); + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 3); + if (result.getExitStatus() != 0) { + logger.error("tpmGitURL:{} 克隆失败!tpiID: {}, e: {}, cmd: {}", tpmGitURL, tpiID, result, cmd); + return false; + } + logger.info("远程克隆成功!tpiId: {}", tpiID); + return true; + } + + public boolean cloudHostPull(String tpiID, String cloudHostConn, String tpiWorkSpace, String tpiGitURL, String tpiRepoName, String codeWorkspace) { + // 执行pull操作 + String cmd = cloudHostConn + "'cd " + tpiWorkSpace + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + "&& git config core.fileMode false" + // 忽略文件权限 + " && git remote set-url origin " + tpiGitURL + " && git pull origin master'"; + + logger.info("开始远程pull!cmd: {}, tpiId: {}", cmd, tpiID); + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 3); + + if (result.getExitStatus() != 0) { + // pull失败备份重新克隆 + String repoName = diskService.buildTpiRepoPath(tpiWorkSpace, tpiRepoName); + String backupCmd = cloudHostConn + "'mv " + repoName + " " + repoName + LocalDateTime.now() + "'"; + ShellUtil.executeAndGetExitStatus(backupCmd); + return cloudHostGitClone(tpiID, cloudHostConn, tpiWorkSpace, tpiGitURL, tpiRepoName, codeWorkspace); + } + + logger.info("远程pull成功!tpiId: {}", tpiID); + return true; + } + + public void cleanSecretFileContent(String secretRepospace) { + File file = FileUtils.getFile(secretRepospace); + try { + cleanFileContent(file); + } catch (IOException e) { + logger.error("清除私密版本库内容失败, secretRepospace: " + secretRepospace + " ,原因: " + e.getMessage()); + } + } + + /** + * 从secret目录拷贝到工作目录 + */ + public void copySecretFileContent(String secretRepospace, String tpiID) throws IOException { + // 获取到secret目录的路径 + String originFilePath = secretRepospace.replace(diskService.getWorkspaceHostPath(tpiID), + diskService.getWorkspaceHostPath(tpiID) + File.separator + DiskService.SECRET_FILE_FOLDER); + File file = FileUtils.getFile(originFilePath); + try { + copyFileContent(file, tpiID); + } catch (IOException e) { + logger.error("复制私密版本库内容失败, secretWorkspace: " + secretRepospace + " ,原因: " + e.getMessage()); + } + } + + private void copyFileContent(File file, String tpiID) throws IOException { + if (file.exists()) { + File targetFile = getTargetSecretFile(file, tpiID); + if (file.isDirectory()) { + if (targetFile.exists()) { + File[] files = file.listFiles(); + if (files != null && files.length > 0) { + for (File subFile : files) { + copyFileContent(subFile, tpiID); + } + } + } else { + FileUtils.copyDirectory(file, targetFile); + } + } else { + if (targetFile.exists()) { + FileUtils.writeStringToFile(targetFile, FileUtils.readFileToString(file)); + } else { + FileUtils.copyFile(file, targetFile); + } + } + } + } + + /** + * 从secret目录路径获取到要拷贝到的工作目录路径 + */ + private File getTargetSecretFile(File originSecretFile, String tpiID) { + String originPath = originSecretFile.getPath(); + String targetPath = originPath.replaceFirst( + diskService.getWorkspaceHostPath(tpiID) + File.separator + DiskService.SECRET_FILE_FOLDER, diskService.getWorkspaceHostPath(tpiID)); + return FileUtils.getFile(targetPath); + } + + private void cleanFileContent(File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + File[] subFiles = file.listFiles(); + if (subFiles != null && subFiles.length > 0) { + for (File subFile : subFiles) { + cleanFileContent(subFile); + } + } + } else { + if (GameHelper.isPic(file.getName())) { + FileUtils.forceDelete(file); + } else { + FileUtils.writeStringToFile(file, ""); + } + } + } + } + + /** + * 为评测服务的git pull方法 remoteName: origin + * + * @param tpiWorkspace + * @param tpiGitURL + * @throws BusinessException + */ + public void gitPull(String tpiID, String tpiWorkspace, String tpiGitURL, String tpiRepoName, Integer contentModified) throws Exception { + String tpiRepoPath = diskService.buildTpiRepoPath(tpiWorkspace, tpiRepoName); + // pull 操作之前,如果tpi本地版本库不存在,则克隆,主机名为origin + File file = FileUtils.getFile(tpiRepoPath); + if (!file.exists()) { + logger.info("版本库不存在,先克隆! tpiGitURL: {}, tpiID: {}", tpiGitURL, tpiID); + try { + gitClone(tpiWorkspace, tpiGitURL, "origin", tpiRepoName, "--depth=1"); + return; + } catch (BusinessException gameException) { + if (!gameException.getErrCode().equals(ErrorCodeEnum.CLONE_FAIL.getValue())) { + throw gameException; + } + } + } + // pull之前,先判断版本库的origin主机是否存在,若不存在,则先创建 + gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + + // 执行pull操作 + String pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + "&& git config core.fileMode false" + // 忽略文件权限 + " && git remote set-url origin " + tpiGitURL + " && git pull origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + String out = result.getOut(); + logger.info("git pull, command: {}, out: {}", pullCommand, out); + if (out.contains("couldn't find remote ref master")) { + logger.info("版本库为空, tpiId:{}, cmd: {}", tpiID, pullCommand); + return; + } + + if (out.contains("commit your changes or stash") || out.contains("would be overwritten by merge")) { + String stashCmd = "cd " + tpiRepoPath + " && git stash save " + TimeHelper.getCurrentTime("yyyyMMddHHmmss"); + logger.info("tpi {} git pull与未commit代码冲突,先保存本地代码, cmd:{}", tpiID, stashCmd); + result = ShellUtil.executeAndGetExitStatus(stashCmd, 1); + if (result.getExitStatus() != 0) { + if (result.getOut().contains("You do not have the initial commit yet")) { + logger.warn("本地版本库损坏,出现You do not have the initial commit yet错误, 备份重新克隆,tpiID: {}", tpiID); + backupPullError(tpiWorkspace, tpiRepoPath, tpiID); + gitClone(tpiWorkspace, tpiGitURL, "origin", tpiRepoName, "--depth=1"); + } else { + logger.error("tpi {} git pull与未commit代码冲突,保存本地代码失败, cmd:{}, error: {}", tpiID, stashCmd, + result.getOut()); + throw new BusinessException(ErrorCodeEnum.CLONE_FAIL.getValue(),"git pull与未commit代码冲突,保存本地代码失败,tpiID:" + tpiID); + } + } + logger.info("tpi {} git pull与未commit代码冲突, 保存本地代码代码后, 再次pull", tpiID); + result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + } + out = result.getOut(); + if (out.contains("Merge conflict") || out.contains("have unmerged files")) { + String resetCmd = "cd " + tpiRepoPath + " && git reset --hard FETCH_HEAD "; + logger.info("tpi {} git pull与本地commit冲突, 使用远程版本, cmd:{}", tpiID, resetCmd); + ShellResult resetResult = ShellUtil.executeAndGetExitStatus(resetCmd, 1); + if (resetResult.getExitStatus() != 0) { + logger.error("tpi {}git pull与本地commit冲突, 使用远程版本失败, cmd:{}, error: {}", tpiID, resetCmd, + resetResult.getOut()); + throw new BusinessException(ErrorCodeEnum.CLONE_FAIL.getValue(), "git pull与本地commit冲突, 使用远程版本失败,tpiID:" + tpiID); + } else { + return; + } + } + if (result.getOut().contains("Username for")) { + logger.info("git credential缺失!重新设置并pull, cmd: {}, result: {}", pullCommand, result); + gitAccountService.gitCredentialStore(gitService.getGitUrlComponent(tpiGitURL, GitUrlComponent.HOST), gitService.getGitUrlComponent(tpiGitURL, GitUrlComponent.PROTOCOL)); + gitPull(tpiID, tpiWorkspace, tpiGitURL, tpiRepoName, contentModified); + return; + } + + + if (result.getExitStatus() != 0) { + logger.warn("tpiID {} git pull失败,将备份后重新clone, pullCommand: {}, 结果: {}", tpiID, pullCommand, result.getOut()); + // 如果pull失败,备份后,重新clone + backupPullError(tpiWorkspace, tpiRepoPath, tpiID); + // 重新clone + gitClone(tpiWorkspace, tpiGitURL, "origin", tpiRepoName, "--depth=1"); + } else { + // 判段是否成功拉取到更新 + int rePullTimes = 10; + while (contentModified != null && contentModified == 1 && rePullTimes > 0 + && "Already up-to-date.".equals(result.getOut())) { + ThreadUtils.sleep(200); + result = ShellUtil.executeAndGetExitStatus(pullCommand, 1); + rePullTimes--; + } + if (rePullTimes == 0) { + logger.error("版本库有更新,2s之内拉取版本库更新失败!, tpiGitURL: {}, tpiID: {}", tpiGitURL, tpiID); + } else { + logger.debug("pull成功, tpiGitURL: {}, tpiID: {}", tpiGitURL, tpiID); + } + } + } + + private void backupPullError(String path, String tpiRepoPath, String tpiID) { + try { + // 若pull失败,则备份旧版本库,重新clone + logger.warn("gitPull冲突,备份旧版本库, tpiID [{}]", tpiID); + // 备份旧文件夹 + Files.move(Paths.get(tpiRepoPath), Paths.get(path + File.separator + LocalDateTime.now())); + } catch (Exception e) { + // 备份失败就强行删除 + logger.warn("tpiID [{}] Pull冲突,备份冲突文件夹失败:{}", tpiID, e.getMessage()); + try { + FileUtils.forceDelete(new File(tpiRepoPath)); + } catch (IOException e1) { + logger.error("tpiID [{}] Pull冲突,冲突文件夹删除失败: {}", tpiID, e.getMessage()); + } + } + } + + /** + * 版本库更新 + * + * @param tpiWorkspace + * @param tpmGitURL + * @param tpiRepoName + * @throws BusinessException + */ + public void gitReset(String tpiWorkspace, String tpmGitURL, String tpiGitURL, String tpiRepoName, String tpiID) + throws BusinessException { + String tpiRepoPath = tpiWorkspace + File.separator + tpiRepoName; + File file = new File(tpiRepoPath); + if (file.exists()) { + // pull tpi + gitPullFromTpi(tpiWorkspace, tpiRepoPath, tpiGitURL, tpiID, tpiRepoName); + + // pull tpm + gitPullFromTpm(tpiWorkspace, tpiRepoPath, tpmGitURL, tpiGitURL, tpiID, tpiRepoName); + } else { + // pull 操作之前,如果本地版本库不存在,则克隆,主机名为remote_origin + gitClone(tpiWorkspace, tpmGitURL, "remote_origin", tpiRepoName, ""); + + // 从tpi pull操作 + gitPullForReset(tpiWorkspace, tpiRepoPath, tpmGitURL, tpiGitURL, tpiID, tpiRepoName); + } + // push to tpi + gitPushToTpi(tpiRepoPath, tpiGitURL, tpiID, null); + } + + public void gitPullFromTpi(String path, String tpiRepoPath, String tpiGitURL, String tpiID, String tpiRepoName) throws BusinessException { + // pull之前,先判断版本库的origin主机是否存在,若不存在,则先创建 + gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + + // 执行pull操作 + String pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder" + " && git config core.fileMode false" + // 忽略文件权限 + " && git remote set-url origin " + tpiGitURL + " && git pull --unshallow origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(pullCommand, 1); + logger.debug("{} gitPullFromTpi git pull unshallow result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + + // 版本库完整时,执行git pull --unshallow报已是完整存储库错误 + if (result.getOut().contains("complete repository")) { + pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + "&& git config core.fileMode false" + // 忽略文件权限 + "&& git remote set-url origin " + tpiGitURL + " && git pull origin master"; + result = ShellUtil.executeAndGetExitStatus(pullCommand, 1); + logger.debug("{} gitPullFromTpi git pull result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + } + if (result.getOut().contains("commit your changes or stash") + || result.getOut().contains("would be overwritten by merge")) { + String stashCmd = "cd " + tpiRepoPath + " && git stash save " + TimeHelper.getCurrentTime("yyyyMMddHHmmss"); + logger.info("tpi {} git pull与未commit代码冲突,先保存本地代码, cmd:{}", tpiID, stashCmd); + result = ShellUtil.executeAndGetExitStatus(stashCmd, 1); + if (result.getExitStatus() != 0) { + logger.error("tpi {} git pull与未commit代码冲突,保存本地代码失败, cmd:{}, error: {}", tpiID, stashCmd, + result.getOut()); + } else { + logger.info("tpi {} git pull与未commit代码冲突, 保存本地代码代码后, 再pull", tpiID); + pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + "&& git config core.fileMode false" + // 忽略文件权限 + "&& git remote set-url origin " + tpiGitURL + " && git pull origin master"; + result = ShellUtil.executeAndGetExitStatus(pullCommand, 1); + } + } + if (result.getOut().contains("Merge conflict") + || result.getOut().contains("have unmerged files")) { + String resetCmd = "cd " + tpiRepoPath + " && git reset --hard FETCH_HEAD "; + logger.info("tpi {} git pull与本地commit冲突, 使用远程版本, cmd:{}", tpiID, resetCmd); + ShellResult resetResult = ShellUtil.executeAndGetExitStatus(resetCmd, 1); + if (resetResult.getExitStatus() != 0) { + logger.error("tpi {}git pull与本地commit冲突, 使用远程版本失败, cmd:{}, error: {}", tpiID, resetCmd, + resetResult.getOut()); + } + } + + if (result.getExitStatus() != 0) { + logger.warn("gitPullFromTpi: pull失败, 备份本地库, clone tpi版本库, tpiId [{}] cmd [{}] error[{}]", tpiID, pullCommand, + result.getOut()); + backupPullError(path, tpiRepoPath, tpiID); + gitClone(path, tpiGitURL, "origin", tpiRepoName, "--depth=1"); + } else { + logger.info("gitPullFromTpi: pull成功, tpiGitURL: {}, tpiID: {}", tpiGitURL, tpiID); + } + } + + private void gitPullFromTpm(String path, String tpiRepoPath, String tpmGitURL, String tpiGitURL, String tpiID, + String tpiRepoName) throws BusinessException { + // pull之前,先判断版本库的remote_origin主机是否存在,若不存在,则先创建 + gitAddRemote("remote_origin", tpiRepoPath, tpmGitURL, tpiID); + + // 执行pull操作 + String pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + " && git config core.fileMode false" + // 忽略文件权限 + " && git remote set-url remote_origin " + tpmGitURL + " && git pull remote_origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + logger.debug("{} gitPullFromTpm git pull result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + + // tpi版本库无修改时,本地版本库未commit的修改在此缓存 + if (result.getOut().contains("commit your changes or stash") + || result.getOut().contains("would be overwritten by merge")) { + String stashCmd = "cd " + tpiRepoPath + " && git stash save " + TimeHelper.getCurrentTime("yyyyMMddHHmmss"); + logger.info("tpi {} git pull与未commit代码冲突,先保存本地代码, cmd:{}", tpiID, stashCmd); + result = ShellUtil.executeAndGetExitStatus(stashCmd, 1); + if (result.getExitStatus() != 0) { + logger.error("tpi {} git pull与未commit代码冲突,保存本地代码失败, cmd:{}, error: {}", tpiID, stashCmd, + result.getOut()); + } else { + logger.info("tpi {} git pull与未commit代码冲突, 保存本地代码代码后, 再pull", tpiID); + result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + } + } + + // 处理tpi和tpm的独立合并 + if (result.getOut().contains("refusing to merge unrelated histories")) { + String allowPullCmd = "git pull remote_origin master --allow-unrelated-histories"; + logger.info("tpi {} 处理tpi和tpm的独立合并, cmd:{}", tpiID, allowPullCmd); + result = ShellUtil.executeAndGetExitStatus(allowPullCmd, 3); + if (result.getExitStatus() != 0) { + logger.error("tpi {} 处理tpi和tpm的独立合并失败, cmd:{}, error: {}", tpiID, allowPullCmd, + result.getOut()); + } else { + logger.info("tpi {} git pull与未commit代码冲突, 保存本地代码代码后, 再pull", tpiID); + result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + } + } + + if (result.getExitStatus() != 0) { + // 文件冲突中会出现这两种错误 + if (result.getOut().contains("Merge conflict") + || result.getOut().contains("have unmerged files")) { + // 更新冲突文件 + List conflictFiles = getConflictFiles(tpiRepoPath); + // 过滤掉不是文件冲突导致的 unmerged 错误 + if (conflictFiles != null && conflictFiles.size() > 0) { + String tpiRepoNameTemp = tpiRepoName + "Temp"; + try { + gitClone(path, tpmGitURL, "remote_origin", tpiRepoNameTemp, "--depth=1"); + updateConflictFiles(conflictFiles, path, tpiRepoName, tpiRepoNameTemp); + logger.info("gitPullFromTpm: 冲突文件更新成功, tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID); + return; + } catch (Exception e) { + logger.error("gitPullFromTpm 冲突文件更新失败 tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID, e); + } finally { + try { + FileUtils.deleteDirectory(new File(path + File.separator + tpiRepoNameTemp)); + } catch (IOException e) { + // ingore quietly + } + } + } + } + logger.warn("gitPullFromTpm: pull代码冲突,将备份到本地,然后强推, tpiID: {} out: {}", tpiID, result.getOut()); + backupPullError(path, tpiRepoPath, tpiID); + gitClone(path, tpmGitURL, "remote_origin", tpiRepoName, ""); + gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + logger.info("gitPullFromTpm: 结束, tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID); + } else { + logger.info("gitPullFromTpm: 成功, tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID); + } + } + + private void updateConflictFiles(List conflictFiles, String path, String tpiRepoName, String tpiRepoNameTemp) throws IOException { + for (String conflictFileStr : conflictFiles) { + File tempFile = new File(path + File.separator + tpiRepoNameTemp + File.separator + conflictFileStr); + String tempContent = FileUtils.readFileToString(tempFile, "UTF-8"); + + File conflictFile = new File(path + File.separator + tpiRepoName + File.separator + conflictFileStr); + FileUtils.writeStringToFile(conflictFile, tempContent, "UTF-8"); + } + + } + + private List getConflictFiles(String tpiRepoPath) { + String cmd = "cd " + tpiRepoPath + " && git status | grep 'both ' | awk '{ print $3 \",\"}'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + if (result.getExitStatus() == 0) { + String conflictOut = result.getOut(); + if (StringUtils.isNotEmpty(conflictOut)) { + return Arrays.asList(conflictOut.split(",")); + } + } + return null; + } + + /** + * 本地push到tpi + * + * @param tpiRepoPath + * @param tpiGitURL + * @throws BusinessException + */ + public void gitPushToTpi(String tpiRepoPath, String tpiGitURL, String tpiID, String type) throws BusinessException { + // 忽略本地新增和删除文件 + String addCmd = "git status | grep \"modified:\" | awk -F[:] '{print $2}' | xargs git add"; + if (TpCsts.JUPYTER_TYPE_LAB.equals(type)) { + addCmd = sysConfigService.getPushToTpiAddCmd(); + } + + // 执行push + String cmd = "cd " + tpiRepoPath + " && git remote set-url origin " + tpiGitURL + + " && git config user.email educoder@163.com && git config user.name educoder" + + " && " + addCmd + + " && git commit -m '" + TimeHelper.getCurrentTime() + "'" + " && git push origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + logger.debug("{} gitPushToTpi git push result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + + // 本地版本库所有修改已commit时,执行git commit报"nothing to commit"错误 + if (result.getOut().contains("nothing to commit")) { + cmd = "cd " + tpiRepoPath + " && git push origin master"; + result = ShellUtil.executeAndGetExitStatus(cmd, 3); + logger.debug("{} gitPushToTpi git push result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + } + if (result.getExitStatus() != 0) { + // 普通push异常,直接push -f强推 + cmd = "cd " + tpiRepoPath + " && git push -f origin master"; + result = ShellUtil.executeAndGetExitStatus(cmd, 3); + } + if (result.getExitStatus() != 0) { + logger.error("gitPushToTpi: push失败, tpiId [{}] cmd [{}] error[{}]", tpiID, cmd, result.getOut()); + throw new BusinessException(ErrorCodeEnum.CLONE_FAIL.getValue(), "push失败,版本库地址:" + tpiGitURL + " tpiID:" + tpiID); + } else { + logger.info("gitPushToTpi: push成功! tpiId [{}] cmd [{}]", tpiID, cmd); + } + } + + public void gitPullForReset(String path, String tpiRepoPath, String tpmGitURL, String tpiGitURL, String tpiID, + String tpiRepoName) throws BusinessException { + // pull之前,先判断版本库的origin主机是否存在,若不存在,则先创建 + gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + + // 执行pull操作 + String pullCommand = "cd " + tpiRepoPath + " && git config user.email educoder@163.com" + + " && git config user.name educoder " + " && git config core.fileMode false" + // 忽略文件权限 + " && git remote set-url origin " + tpiGitURL + " && git pull origin master"; + ShellResult result = ShellUtil.executeAndGetExitStatus(pullCommand, 3); + logger.debug("{} gitPullForReset git pull result: {}, out: {}", tpiID, result.getExitStatus(), + result.getOut()); + if (result.getExitStatus() != 0) { + // 文件冲突中会出现这两种错误 + if (result.getOut().contains("Merge conflict") + || result.getOut().contains("have unmerged files")) { + // 更新冲突文件 + List conflictFiles = getConflictFiles(tpiRepoPath); + if (conflictFiles != null && conflictFiles.size() > 0) { + String tpiRepoNameTemp = tpiRepoName + "Temp"; + try { + gitClone(path, tpmGitURL, "remote_origin", tpiRepoNameTemp, "--depth=1"); + updateConflictFiles(conflictFiles, path, tpiRepoName, tpiRepoNameTemp); + logger.info("gitPullForReset: pull成功, tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID); + return; + } catch (Exception e) { + logger.warn("gitPullForReset 冲突文件更新失败 tpmGitURL: {}, tpiID: {}", tpmGitURL, tpiID); + } finally { + try { + FileUtils.deleteDirectory(new File(path + File.separator + tpiRepoNameTemp)); + } catch (IOException e) { + // ingore quietly + } + } + } + } + logger.warn("gitPullForReset: pull代码冲突,将备份到本地,然后强推, tpiID: {} out: {}", tpiID, result.getOut()); + backupPullError(path, tpiRepoPath, tpiID); + gitClone(path, tpmGitURL, "remote_origin", tpiRepoName, ""); + gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + } + logger.info("gitPullForReset: pull成功, tpiGitURL: {}, tpiID: {}", tpiGitURL, tpiID); + } + + public void gitAddRemote(String remoteName, String repoPath, String gitURL, String tpiID) throws BusinessException { + String cmd = "cd " + repoPath + " && git remote | grep ^" + remoteName + "$"; + ShellResult remoteExist = ShellUtil.executeAndGetExitStatus(cmd); + if (remoteExist.getExitStatus() != 0 || !remoteExist.getOut().contains(remoteName)) { + if (remoteExist.getOut().contains("fatal")) { + logger.warn("{} 版本库不完整,先进行init操作, gitURL: {}, tpiID: {}", remoteName, gitURL, tpiID); + cmd = "cd " + repoPath + " && git init"; + ShellUtil.execute(cmd); + } + logger.warn("{} 主机名不存在,先添加主机名及其git地址, gitURL: {}, tpiID: {}", remoteName, gitURL, tpiID); + cmd = "cd " + repoPath + " && git remote add " + remoteName + " " + gitURL; + ShellResult addRemote = ShellUtil.executeAndGetExitStatus(cmd, 1); + if (addRemote.getExitStatus() != 0) { + logger.error("{} 主机名不存在,添加主机名失败, tpiID: {} cmd: {}", remoteName, tpiID, cmd); + throw new BusinessException(ErrorCodeEnum.VERSION_REPOSITORY_NOT_EXIST.getValue(), remoteName + "主机名不存在,无法完成git操作,tpiID:" + tpiID); + } else { + logger.info("{} 主机名不存在,添加主机名成功, tpiID: {} cmd: {}", remoteName, tpiID, cmd); + } + } + } + + /** + * 对评测shell脚本的结果再处理传递给实训前台 注意pass与status的区别,遗留问题。 pass指单个测试用例是否通过,1 + * 为通过,0为不通过,status指整个评测是否通过,0为通过,-1为不通过 + * + * @param buildResult + * shell 评测脚本执行的结果 + * @param buildParams + * 本次评测的其他参数,如buildID,resubmit + * @param cases + * 本关测试用例 + * @return 评测结果 + */ + public BuildResult encapsulateBuildResult(String cluster, String buildResult, JSONObject buildParams, JSONArray cases) { + Instant encapsulateStartInstant = Instant.now(); + // 从评测脚本执行的结果中取得编译结果,测试用例的输入,预期输出与输出 + JSONObject buildResultInJson = JSONObject.parseObject(buildResult); + // 解码再编码是因为shell脚本中的编码是非URL safe编码 + String compileResult = Base64Util.reencode(buildResultInJson.getString("compileResult")); + JSONArray outs = buildResultInJson.getJSONArray("out"); + // 初始化结果对象,设置outPut字段为编译结果 + String tpiID = buildParams.getString("tpiID"); + Integer podType = buildParams.getInteger("podType"); + String buildID = buildParams.getString("buildID"); + List msg = new ArrayList<>(); + Boolean isLocal = buildParams.get("isLocal") == null ? Boolean.FALSE : buildParams.getBoolean("isLocal"); + Integer nodePort = buildParams.getInteger("nodePort"); + String showServer = nodePort == null ? null : (clusterConfigService.getShowServer(cluster, isLocal) + ":" + nodePort); + BuildResult result = new BuildResult(buildID, tpiID, "0", compileResult, msg, buildParams.getString("resubmit"), + buildParams.getString("sec_key"), showServer, buildParams.getString("extras")); + result.setTimeoutCode(buildParams.getInteger("timeoutCode")); // TODO 不应放BuildParams 里面 + String downloadStatus = buildResultInJson.getString(BuildResultCsts.DOWNLOAD_STATUS); + downloadStatus = downloadStatus == null ? BuildResultCsts.DOWNLOAD_STATUS_SUCCESS : downloadStatus; + result.setDownloadStatus(downloadStatus); + String createPodStatus = buildResultInJson.getString(BuildResultCsts.CREATE_POD_STATUS); + createPodStatus = createPodStatus == null ? BuildResultCsts.CREATE_POD_STATUS_SUCCESS : createPodStatus; + result.setCreatePodStatus(createPodStatus); + + String resUsageStr = buildResultInJson.getString("resUsage"); + ResUsage resUsage = this.buildResUsage(cases, resUsageStr); + // 如果编译输出太大,截断 + int maxLength = appConfig.getOutputSize(); + if (compileResult.getBytes().length > maxLength) { + compileResult = new String(Arrays.copyOf(compileResult.getBytes(), maxLength)); + result.setOutPut(compileResult); + } + + + // 兼容脚本没有更新的情况,compileCode和executeCode不存在 + Integer compileCode = buildResultInJson.getInteger("compileCode"); + if(compileCode == null){ + // 判断是否编译成功 + if (BuildResultCsts.COMPILE_SUCCESS_BASE64.equals(compileResult) + || BuildResultCsts.EVA_OVER_MEMORY_LIMIT_BASE64_REENCODE.equals(compileResult) + || BuildResultCsts.CLONE_CODE_FAIL_BASE64_REENCODE.equals(compileResult)) { + compileCode = 0; + } else { + compileCode = 1; + } + } + int executeCode = buildResultInJson.getIntValue("executeCode"); + + result.setCompileSuccess(compileCode == 0 ? BuildResultCsts.COMPILE_SUCCESS_YES : BuildResultCsts.COMPILE_SUCCESS_NO); + result.setExecuteSuccess(executeCode == 0 ? BuildResultCsts.COMPILE_SUCCESS_YES : BuildResultCsts.COMPILE_SUCCESS_NO); + + Integer trimBlank = buildParams.getInteger("trimBlank"); + // 对于每一个测试用例,判断其输出是否等于预期输出 + for (int i = 0; i < cases.size(); i++) { + String pass = ExecResultCase.Status.PASS.getValue(); + // 解码后比较 + String outI = null; + if (i < outs.size()) { + outI = Base64Util.decode(outs.getString(i)).replace("\r\n", "\n").replace("\r", "\n").replace("\u0000", ""); + // 如果输出结果过大,提示错误,并将输出截断显示 + if (outs.getString(i).getBytes().length > maxLength) { + outs.set(i, Base64Util.encode( + "评测输出结果过长,请检查代码逻辑,部分输出如下:\n" + new String(Arrays.copyOf(outI.getBytes(), maxLength)))); + } + } + // 文件类型比较 + if (ExecSourceCase.Type.FILE.getValue().equals(cases.getJSONObject(i).getString("type"))) { + pass = execResultService.dealWithFileTypeCase(cases.getJSONObject(i), outI); + } else { // 文本类型比较 + String matchRule = cases.getJSONObject(i).getString("matchRule"); + String expectedOutI = cases.getJSONObject(i).getString("output").replace("\r\n", "\n").replace("\r", "\n"); + Integer testCasesType = buildParams.getInteger("testCasesType"); + String testCasesExp = buildParams.getString("testCasesExp"); + // 如果是表达式判断类型 + if ((testCasesType != null && testCasesType == 1) && StringUtils.isNotEmpty(testCasesExp)) { + outI = trim(outI, trimBlank); + expectedOutI = trim(expectedOutI, trimBlank); + + // 末尾匹配,截取实际输出的最后一行 + if ("last".equals(matchRule) && StringUtils.isNotEmpty(outI) && outI.contains("\n")) { + outI = outI.substring(outI.lastIndexOf("\n") + 1, outI.length()); + } + if (StringUtils.isEmpty(outI)) { + pass = "0"; + } else { + Boolean aviatorSuccess = false; + try { + logger.info("aviatorEvaluator testCasesExp: {}, expectedOutI: {}, outI: {}", Base64Util.decode(testCasesExp), expectedOutI, outI); + aviatorSuccess = AviatorUtils.aviatorEvaluator(Base64Util.decode(testCasesExp), expectedOutI, outI); + } catch (Exception e) { + logger.info("评测表达式判定错误, tpiID: {}", tpiID, e); + outs.set(i, Base64Util.encode("评测表达式执行错误:\n" + e + "\n" + Base64Util.decode(outs.getString(i)))); + } finally { + if (!aviatorSuccess) { + pass = "0"; + } + } + } + } else { + // 如果不等,设置本次评测结果为不通过,并设置本测试用例不通过 + // 根据匹配规则判断 + if ("last".equals(matchRule)) { + if (outI == null) { + pass = ExecResultCase.Status.FAIL.getValue(); + } else if (!outI.endsWith(expectedOutI)) { + String trimedExpectedOutI = trim(expectedOutI, trimBlank); + String trimedOutI = trim(outI, trimBlank); + if (trimedExpectedOutI == expectedOutI && trimedOutI == outI) {// 清理首尾,未改变,无需比较 + pass = ExecResultCase.Status.FAIL.getValue(); + } else if (!trimedOutI.endsWith(trimedExpectedOutI)) {// 清理首尾,再比较 + pass = ExecResultCase.Status.FAIL.getValue(); + } + } + } else { + if (!expectedOutI.equals(outI)) { + String trimedExpectedOutI = trim(expectedOutI, trimBlank); + String trimedOutI = trim(outI, trimBlank); + if (trimedExpectedOutI == expectedOutI && trimedOutI == outI) {// 清理首尾,未改变,无需比较 + pass = ExecResultCase.Status.FAIL.getValue(); + } else if (!trimedExpectedOutI.equals(trimedOutI)) {// 清理首尾,再比较 + pass = ExecResultCase.Status.FAIL.getValue(); + } + } + } + } + } + + // 如果本case不通过,置整个状态为失败 + if (ExecResultCase.Status.FAIL.getValue().equals(pass)) { + result.setStatus(BuildResult.Status.FAIL.getValue()); + } + + ExecResultCase testExecResultCase = new ExecResultCase((i + 1) + "", (i >= outs.size()) ? "" : Base64Util.reencode(outs.getString(i)), + pass, cases.getJSONObject(i).getString("type")); + testExecResultCase.setTestSetTime(resUsage.getTestSetUsages().get(i).getTestSetTime()); + testExecResultCase.setTestSetMem(resUsage.getTestSetUsages().get(i).getTestSetMem()); + msg.add(testExecResultCase); + } + + //假如是jupyter评测,执行自定义评测脚本,最终决定评测结果,若评测成功则不会封装错误信息 + if(podType == TpCsts.POD_TYPE_JUPYTER){ + evaluateJupyter(cluster,result,buildParams,buildResult,cases,msg); + } + + + // 如果编译不通过,置整体状态为失败 + if (BuildResultCsts.COMPILE_SUCCESS_NO.equals(result.getCompileSuccess())) { + result.setStatus(BuildResult.Status.FAIL.getValue()); + } + + // 假如运行结果不匹配,封装出错误信息 + StringBuilder errorInfo = new StringBuilder(); + if (BuildResult.Status.FAIL.getValue().equals(result.getStatus()) && BuildResultCsts.COMPILE_SUCCESS_YES.equals(result.getCompileSuccess())) { + errorInfo.append("共有").append(result.getMsg().size()).append("组测试集,其中有"); + int failedCount = 0; + for (int i = 0; i < result.getMsg().size(); i++) { + if (ExecResultCase.Status.FAIL.getValue().equals(result.getMsg().get(i).getPassed())) { + failedCount = failedCount + 1; + } + } + errorInfo.append(failedCount).append("组测试结果不匹配。详情如下:"); + if (!BuildResultCsts.EVA_OVER_MEMORY_LIMIT_BASE64_REENCODE.equals(compileResult) + && !BuildResultCsts.CLONE_CODE_FAIL_BASE64_REENCODE.equals(compileResult) ) { + result.setOutPut(Base64Util.encode(errorInfo.toString())); + } + } + // 封装调试结果 + if (!StringUtils.isEmpty(buildResultInJson.getString("oriOut"))) { + result.setOutPut(Base64Util.encode(GameHelper.getDebugOutPut(Base64Util.decode(result.getOutPut()), + buildResultInJson.getString("oriOut")))); + } + // 封装结果完毕,将封装评测结果的耗时计入评测耗时中 + TimeCost timeCost = TimeCostUtils.getTimeCost(buildParams, encapsulateStartInstant); + + // 如果是仅运行,写redis + if (CommonStateEnum.fromInteger(buildParams.getInteger("runOnly")) == CommonStateEnum.TRUE) { + // 运行完成之后在redis中存储一个key,key为前缀+tpiID,value为运行或者编译结果 + execResultService.saveRunOnlyResultToRedis(tpiID, compileResult, result.getMsg().stream().map(ExecResultCase::getOutput).collect(Collectors.toList())); + } + + // 将结果转化为String类型传回educoder + String resultStr = JSONObject.toJSONString(result); + String costStr = JSONObject.toJSONString(timeCost); + Map map = new HashMap<>(); + map.put("jsonTestDetails", resultStr); + map.put("timeCost", costStr); + + logger.info(tpiID + " 评测各阶段耗时:" + timeCost.toString() + ", 输出:" + resultStr); + + String podTpiRepoPath = appConfig.getWorkspace() + File.separator + TpCsts.TP_UNIFY_REPO_NAME; + String tpiRepoPath = buildParams.getString("tpiRepoPath"); + map.put("tpiRepoPath", tpiRepoPath); + // 若生成了文件 + if (buildParams.containsKey("file")) { + JSONObject fileObeject = JSONObject.parseObject(buildParams.getString("file")); + String filePath = tpiRepoPath + File.separator + fileObeject.getString("path"); + String podFilePath = podTpiRepoPath + File.separator + fileObeject.getString("path"); + if ("apk".equals(fileObeject.getString("type"))) { + String apkName = GameHelper.getApkName(filePath); + if (!"".equals(apkName)) { + // apk文件需要具体到对应的文件名 + podFilePath = podFilePath + File.separator + apkName; + filePath = filePath + File.separator + apkName; + map.put("tpiRepoPath", filePath); + } else { + map.remove("tpiRepoPath"); + } + } + // 复制远程节点生成文件回本地 + if (!clusterManager.isLocalCluster(cluster)) { + Integer timeLimit = buildParams.getInteger("timeLimit"); + String podName = buildParams.getString("podName"); + if (StringUtils.isNotEmpty(podName)) { + String cp = " timeout " + timeLimit + " kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " cp " + podName + ":" + podFilePath + " " + filePath; + ShellResult cpResult = ShellUtil.executeAndGetExitStatus(cp); + logger.info("复制远程节点生成文件回本地,pod: {}, cmd: {}, filePath: {}, exitStatus: {}, out: {}", podName, cp, filePath, cpResult.getExitStatus(), + cpResult.getOut()); + } + } + // 赋权 + ShellUtil.execute("chmod -R 777 " + tpiRepoPath); + } + educoderService.commitResultToEducoder(buildParams.getString("callBackUrl"), map); + + //若硬件评测不走此逻辑 + if(buildParams.getInteger("hardware") == null){ + // 移除正在评测缓存 + clearRedoEvaluating(tpiID); + bridgePodService.updateBridgeInfo(buildParams, timeCost, result); + } + + boolean r = "0".equals(result.getStatus()); + logger.debug("tpiID:{},本关{}", tpiID, r ? "通过" : "未通过"); + return result; + } + + /** + * 假如是jupyter的评测,执行自定义评测脚本,最终决定评测结果 + * @param cluster + * @param result + * @param buildParams + * @param buildResult + * @param cases + */ + private void evaluateJupyter(String cluster, BuildResult result, JSONObject buildParams, String buildResult, JSONArray cases, List msg) { + //base64加密执行结果json + String encodeResult = Base64.encode(buildResult); + //base64加密测试用例 + String caseOutput = Base64.encode(JSON.toJSONString(cases)); + //执行自定义评测脚本 + String shFile = TpCsts.TPI_PROTECT_SPACE_MOUNT_PATH + "/evaluate.py"; + String podName = TpUtils.buildEvaPodName(buildParams.getString("tpiID"),buildParams.getInteger("podType")); + String command = "kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " exec " + podName + + " -- python3 " + shFile + " " + encodeResult + " " + caseOutput; + logger.info("执行评测, pod: {}, command: {}", podName, command); + //执行代码,获取结果 + ShellResult rst = ShellUtil.executeAndGetErrorStatus(command); + JSONObject buildResultInJson = JSONObject.parseObject(rst.getOut()); + String status = buildResultInJson.getString("status"); + JSONArray caseFailIdx = buildResultInJson.getJSONArray("case_fail_index"); + + //评测成功 + if(status.contains("True")){ + //置整个状态为成功 + result.setStatus(BuildResult.Status.PASS.getValue()); + //若评测成功,则任务所有测试集匹配成功 + for(ExecResultCase item: msg){ + item.setPassed(ExecResultCase.Status.PASS.getValue()); + } + }else{ + result.setStatus(BuildResult.Status.FAIL.getValue()); + + List idx = new ArrayList<>(); + for(int j = 0; j < caseFailIdx.size(); j++){ + if(caseFailIdx.get(j) != null || caseFailIdx.get(j) != ""){ + for(int k = 0; k < cases.size(); k++){ + JSONObject obj = cases.getJSONObject(k); + String str = obj.getString("output").replace("\n",""); + logger.info("yc--------:{}",obj.getString("output")); + logger.info("yc--------:{}",str); + if(caseFailIdx.get(j).equals(str)) { + idx.add(k+1); + } + } + } + } + + logger.info("qb--------:{}",idx); + for(ExecResultCase item: msg){ + if(idx.contains(Integer.parseInt(item.getCaseId()))){ + item.setPassed("0"); + logger.info("qc------",item.getPassed()); + + }else{ + item.setPassed("1"); + logger.info("wc------",item.getPassed()); + + } + } + + } + } + + + private String trim(String out, Integer trimBlank) { + if (trimBlank == null || 0 == trimBlank) { + return out; + } + out = trimHead(out); + out = trimTail(out); + if (2 == trimBlank) { + out = out.replaceAll(" ", ""); + } + return out; + } + + private String trimHead(String str) { + if (StringUtils.isBlank(str)) { + return ""; + } + + //一定存在非空字符,可优化算法 + int index = 0; + char c = str.charAt(index); + while ('\n' == c || ' ' == c) { + index++; + c = str.charAt(index); + } + + if(index == 0) { + return str; + } else { + return str.substring(index); + } + } + + private String trimTail(String str) { + if (StringUtils.isBlank(str)) { + return ""; + } + + //一定存在非空字符,可优化算法 + int lastIndex = str.length() - 1; + int index = lastIndex; + char c = str.charAt(index); + while ('\n' == c || ' ' == c) { + index--; + c = str.charAt(index); + } + + if(index == lastIndex) { + return str; + } else { + return str.substring(0, index + 1); + } + } + + private String educoderRepoPath(String tpiRepoPath) { + tpiRepoPath = tpiRepoPath.substring(1); + int index = tpiRepoPath.indexOf("/"); + tpiRepoPath = tpiRepoPath.substring(index + 1); + return tpiRepoPath; + } + + public ResUsage buildResUsage(JSONArray cases, String resUsageStr) { + ResUsage resUsage = new ResUsage(); + if (StringUtils.isBlank(resUsageStr)) { + List list = new ArrayList<>(); + for (int i = 0; i < cases.size(); i++) { + ResUsage.TestSetUsage tsu = new ResUsage.TestSetUsage(); + list.add(tsu); + } + resUsage.setTestSetUsages(list); + } else { + resUsageStr = Base64Util.decode(resUsageStr); + logger.info("buildResUsage, resUsageStr: {}", resUsageStr); + JSONObject resUsageJson = JSONObject.parseObject(resUsageStr); + resUsage.setRunMaxMem(resUsageJson.getLong("runMaxMem")); + List list = new ArrayList<>(); + JSONArray arr = resUsageJson.getJSONArray("testSetUsages"); + for (int i = 0; i < cases.size(); i++) { + ResUsage.TestSetUsage tsu = new ResUsage.TestSetUsage(); + tsu.setTestSetTime(arr.getJSONObject(i).getLong("testSetTime")); + tsu.setTestSetMem(arr.getJSONObject(i).getLong("testSetMem")); + list.add(tsu); + } + resUsage.setTestSetUsages(list); + } + return resUsage; + } + + /** + * 分步结果 + * + * @param textMsg + * @param status + * @param ratio + * @param buildID + * @return + */ + public boolean encapsulateSimpleBuildResult(String url, String textMsg, Integer status, Integer ratio, String buildID) { + // 如果编译输出太大,截断 + int maxLength = appConfig.getOutputSize(); + // 如果输出结果过大,提示错误,并将输出截断显示 + String str = "消息输出结果过长,部分输出如下:\\n"; + if (textMsg.getBytes().length > maxLength) { + textMsg = str + new String(Arrays.copyOf(textMsg.getBytes(), maxLength - str.length())); + } + SimpleBuildResult simpleResult = new SimpleBuildResult(); + simpleResult.setBuildID(buildID); + simpleResult.setStatus(status); + simpleResult.setTextMsg(textMsg); + if (ratio != null) { + simpleResult.setRatio(ratio); + } + String resultStr = JSONObject.toJSONString(simpleResult); + Map map = new HashMap<>(); + map.put("jsonTestDetails", resultStr); + educoderService.commitSimpileResultToEducoder(url, map); + return true; + } + + /** + * 生成TPI评测脚本 + * + * @param tpmScript + * TPM脚本 + * @param tpiProtectSpace + * TPI保护目录 + * @param tpiRepoName + * TPI版本库名字 + * @throws BusinessException + */ + public void generateTpiEvaluateShellScript(String tpmScript, String tpiProtectSpace, String tpiRepoName) + throws BusinessException { + // 设置TPI脚本保护空间 + String tpiScript = tpmScript.replace("WORKSPACE", + diskService.buildTpiRepoPath(appConfig.getWorkspace(), tpiRepoName)); + try { + FileUtils.writeStringToFile(new File(tpiProtectSpace + File.separator + "evaluate.sh"), tpiScript, "UTF-8"); + } catch (Exception e) { + logger.error("生成TPI评测脚本失败!{}", tpiProtectSpace, e); + throw new BusinessException(ErrorCodeEnum.EVALUATION_SHELL_FAIL.getValue(), "生成TPI评测脚本失败:" + tpiProtectSpace); + } + } + + + /** + * 生成TPI评测脚本 - Python评测脚本 + * + * @param tpmScript + * TPM脚本 + * @param tpiProtectSpace + * TPI保护目录 + * @param tpiRepoName + * TPI版本库名字 + * @throws BusinessException + */ + public void generateTpiEvaluatePythonScript(String tpmScript, String tpiProtectSpace, String tpiRepoName) + throws BusinessException { + // 设置TPI脚本保护空间 + String tpiScript = tpmScript.replace("WORKSPACE", + diskService.buildTpiRepoPath(appConfig.getWorkspace(), tpiRepoName)); + try { + FileUtils.writeStringToFile(new File(tpiProtectSpace + File.separator + "evaluate.py"), tpiScript, "UTF-8"); + } catch (Exception e) { + logger.error("生成TPI评测脚本失败!{}", tpiProtectSpace, e); + throw new BusinessException(ErrorCodeEnum.EVALUATION_SHELL_FAIL.getValue(), "生成TPI评测脚本失败:" + tpiProtectSpace); + } + } + + /** + * 根据评测信息,构建一个evaJob + * + * @param buildParams + * @return + */ + public TpEvaJob getTpEvaJob(JSONObject buildParams) { + TpEvaJob tpEvaJob = BeanFactory.getObejct(TpEvaJob.class); + tpEvaJob.setBuildParams(buildParams); + return tpEvaJob; + } + + + + + public LocalCheckJob getLocalCheckJob(String tpiID, String tpiGitURL, String tpiWorkspace) { + LocalCheck localCheck = new LocalCheck(tpiID, tpiGitURL, tpiWorkspace); + LocalCheckJob localCheckJob = BeanFactory.getObejct(LocalCheckJob.class); + localCheckJob.setLocalCheck(localCheck); + return localCheckJob; + } + + /** + * 检查并修复远端版本库 + * + * @param tpiID + * @param tpiGitURL + * @return + * @throws Exception + */ + public JSONObject check(String tpiID, String tpiGitURL) throws Exception { + logger.info("校验head是否存在,tpiID: {}, tpiGitURL: {}", tpiID, tpiGitURL); + JSONObject response = new JSONObject(); + response.put("code", 0); + String tpiRepoName = GameHelper.getGitRepoName(tpiGitURL); + String identifier = GameHelper.getIdentifier(tpiGitURL); + // 第一个步骤,检查远程TPI版本库是否为空 + String commandBase = "ssh -p" + appConfig.getGitPort() + " -o StrictHostKeyChecking=no git@"; + String command = commandBase + appConfig.getGitIP() + " 'ls " + appConfig.getGitRepoDir().trim() + "/" + + identifier + "/" + tpiRepoName + ".git 2>/dev/null | wc -l'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(command); + // 255是连接失败,如果连接成功wc -l总是执行的,对于前面文件夹为空或者不存在的情况,$?总是0,输出结果总是0 + if ((result.getExitStatus() == 0 && result.getOut().equals("0"))) { + // 如果版本库为空,考虑是迁移到了备份机上 + // ssh -p1122 git@10.9.54.50 "mv /home/git/repositories/2018/innov/yvqhfr7a.git/ + // /home/git/repositories/innov/" + boolean recovery = false; + for (String backUpDir : appConfig.getGitBackupDirs().split(",")) { + String mvCommand = "mv " + backUpDir.trim() + "/" + identifier + "/" + tpiRepoName + ".git " + + appConfig.getGitRepoDir().trim() + "/" + identifier + "/"; + command = commandBase + appConfig.getGitBackUpIP() + " '" + mvCommand + "'"; + // 若迁移不成功,判定版本库不存在错误 + if (ShellUtil.executeAndGetExitStatus(command).getExitStatus() == 0) { + recovery = true; + break; + } + } + if (!recovery) { + logger.error("check方法,远程TPI版本库为空,tpiID: {}, cmd: {}", tpiID, command); + response.put("code", -2); + response.put("msg", "finished"); + return response; + } + } + // 第二个步骤,检测远程版本库HEAD是否存在 (.git/refs/heads/master没内容就是HEAD丢失了,git branch + // 就会报错) + command = commandBase + appConfig.getGitIP() + " 'cd " + appConfig.getGitRepoDir().trim() + "/" + identifier + + "/" + tpiRepoName + ".git; git branch'"; + result = ShellUtil.executeAndGetExitStatus(command); + if (result.getExitStatus() != 0) { + logger.warn("check方法,远程tpi版本库head丢失,将进行修复,tpiID: {}", tpiID); + // 处理仓库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`' + command = commandBase + appConfig.getGitIP() + " 'cd " + appConfig.getGitRepoDir().trim() + "/" + identifier + + "/" + tpiRepoName + + ".git/refs/tmp; cd `ls -t | sed -n \"2p\"`; cat head >../../heads/master; git update-ref HEAD `cat head`'"; + result = ShellUtil.executeAndGetExitStatus(command); + if (result.getExitStatus() != 0) { + logger.error("check方法,远程tpi版本库head丢失,修复失败,tpiID: {}, cmd: {}", tpiID, command); + response.put("code", -1); + } else { + logger.warn("check方法,远程tpi版本库head丢失,修复成功,tpiID: {}", tpiID); + } + } + response.put("msg", "finished"); + return response; + } + + public ApiResult checkLocalCode(String tpiID, String tpiGitURL, String tpiWorkspace) throws Exception { + ApiResult result = new ApiResult<>(); + try { + gitPull(tpiID, tpiWorkspace, tpiGitURL, TpCsts.TP_UNIFY_REPO_NAME, 0); + } catch (Exception e) { + logger.warn("local check " + tpiID + "代码失败,可能已经被移动,tpiGitURL:" + tpiGitURL, e); + JSONObject checkResult = check(tpiID, tpiGitURL); + int code = checkResult.getInteger("code"); + if (code == 0) { + gitPull(tpiID, tpiWorkspace, tpiGitURL, TpCsts.TP_UNIFY_REPO_NAME, 0); + + } else { + result.setCode(code); + result.setMsg(checkResult.getString("msg")); + } + } + return result; + } + + + + + /** + * 修改文件内容:字符串逐行替换 + * @param file:待处理的文件 + * @param oldstr:需要替换的旧字符串 + * @param newStr:用于替换的新字符串 + */ + public static boolean modifyFileContent(File file, String oldstr, String newStr) { + List list = null; + try { + list = FileUtils.readLines(file, "UTF-8"); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).indexOf(oldstr) != -1) { + String temp = list.get(i).replaceAll(oldstr, newStr); + list.remove(i); + list.add(i, temp); + } + } + FileUtils.writeLines(file, "UTF-8", list, false); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + + + public void removeTpmRemoteUrl(String tpiRepoPath) { + String cmd = "cd " + tpiRepoPath + + " && git branch --set-upstream-to=origin/master master && git remote rm remote_origin"; + ShellUtil.executeAndGetExitStatus(cmd, 1); + } + + public void generateEvaluateShellScript(String tpiWorkspace, String script) throws BusinessException { + logger.debug("写评测脚本, tpiWorkspace: {}, script: {}", tpiWorkspace, script); + + // 写评测脚本 + script = Base64Util.decode(script); + script = script.replace("WORKSPACE", diskService.buildTpiRepoPath(tpiWorkspace, appConfig.getOjProjectDirname())); + + try { + // 写评测脚本到工作空间目录下 + FileUtils.writeStringToFile(new File(tpiWorkspace + File.separator + "evaluate.sh"), script, "UTF-8"); + } catch (Exception e) { + logger.error("写评测脚本失败!{}", e); + + throw new BusinessException(ErrorCodeEnum.EVALUATION_SHELL_FAIL.getValue(), "写评测脚本失败"); + } + + } + + public void generateEvaluateCodeFile(String tpiWorkspace, String codeFileName, String codeFileContent) + throws BusinessException { + + logger.debug("写代码文件, tpiWorkspace: {}, codeFileName: {}", tpiWorkspace, codeFileName); + String codeFilePath = tpiWorkspace + File.separator + codeFileName; + try { + // 写代码文件到工作空间目录下 + FileUtils.writeStringToFile(new File(codeFilePath), codeFileContent, "UTF-8"); + } catch (Exception e) { + logger.error("写代码文件失败!{}", e); + + throw new BusinessException(ErrorCodeEnum.WRITE_FILE_CODE_FAIL); + } + } + + public void updateTpiFileContent(String tpiRepoPath, String tpiRepoTempPath) throws BusinessException { + try { + String path = tpiRepoPath + File.separator + "01.ipynb"; + String fileContent = FileUtils.readFileToString(new File(tpiRepoTempPath + File.separator + "01.ipynb"), + "UTF-8"); + FileUtils.writeStringToFile(new File(path), fileContent, "UTF-8"); + logger.info("成功更新jupyter tpi:{}", path); + FileUtils.deleteDirectory(new File(tpiRepoTempPath)); + } catch (IOException e) { + logger.error("更新tpi版本库内容失败, tpiRepoPath {}, tpiRepoTempPath {}", tpiRepoPath, tpiRepoTempPath, e); + throw new BusinessException(ErrorCodeEnum.UPDATE_VERSION_REPOSITORY_FAIL.getValue(),"更新tpi版本库内容失败,tpiRepoPath:" + tpiRepoPath); + } + } + + public boolean checkLocalCodeExist(String tpiID, String tpiGitURL, String tpiWorkspace) { + String tpiRepoName = TpCsts.TP_UNIFY_REPO_NAME; + String tpiRepoPath = diskService.buildTpiRepoPath(tpiWorkspace, tpiRepoName); + if (new File(tpiRepoPath).exists()) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + /** + * 判断是否远端仓库,若为远端仓库,发起HTTP请求进行重置 + */ + public Boolean remoteClusterTpmRepositoryHandle(String tpiID, String tpiGitURL, String tpmGitURL, + String tpiWorkspace, String tpiRepoName, String tpiRepoPath) throws BusinessException { + // evassh,webssh,vnc pod更新最近分配到的集群 + String alreadyCluster = JedisUtil.get(TpCsts.STATEFUL_POD_CLUSTER_RECORD_REDIS_KEY + tpiID); + if (StringUtils.isNotEmpty(alreadyCluster) && !clusterManager.isLocalCluster(alreadyCluster)) { + GitResetRequestParam gitResetRequestParam = new GitResetRequestParam(); + gitResetRequestParam.setTpiId(tpiID); + if (!gitService.isRemoteClusterGitUrl(tpiGitURL)) { + tpiGitURL = TpUtils.buildRemoteClusterGitURL(tpiGitURL, appConfig.getGitServiceDomain()); + } + gitResetRequestParam.setTpiGitURL(tpiGitURL); + + if (!gitService.isRemoteClusterGitUrl(tpmGitURL)) { + tpmGitURL = TpUtils.buildRemoteClusterGitURL(tpmGitURL, appConfig.getGitServiceDomain()); + } + gitResetRequestParam.setTpmGitURL(tpmGitURL); + gitResetRequestParam.setTpiWorkspace(tpiWorkspace); + gitResetRequestParam.setTpiRepoName(tpiRepoName); + gitResetRequestParam.setTpiRepoPath(tpiRepoPath); + + Boolean resetFlag = gitService.remoteClusterResetTpmRepository(alreadyCluster, Boolean.TRUE, tpiID, gitResetRequestParam); + if (!resetFlag) { + throw new BusinessException(ErrorCodeEnum.SYNC_CLUSTER_VERSION_REPOSITORY_FAIL.getValue(), "远程集群版本库同步失败 cluster: " + alreadyCluster); + } + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + /** + * @param tpiID + * @param timeLimit + * @return + */ + public void redoEvaluatingHandle(String tpiID, int timeLimit) { + // 评测时存入redis,评测完成立刻删除,即使发生异常未删除,失效时间为 timeLimit。所以存在key,即可判断为重复评测。 + String key = TpCsts.REDO_EVALUATING_REDIS_KEY + tpiID; + if (JedisUtil.exists(key)) { + JedisUtil.psetex(key, TpCsts.REDO_EVALUATING_REDIS_VALUE_TRUE, timeLimit * 1000L); + logger.info("重复评测请求: tpiID: {}", tpiID); + return; + } + JedisUtil.psetex(key, TpCsts.REDO_EVALUATING_REDIS_VALUE_FALSE, timeLimit * 1000L); + } + + public boolean isRedoEvaluating(String tpiID) { + return TpCsts.REDO_EVALUATING_REDIS_VALUE_TRUE.equals(JedisUtil.get(TpCsts.REDO_EVALUATING_REDIS_KEY + tpiID)); + } + + public void clearRedoEvaluating(String tpiID) { + String key = TpCsts.REDO_EVALUATING_REDIS_KEY + tpiID; + String redoEvaluating = JedisUtil.get(key); + // 若为重复评测,则延迟5秒后删除该记录,利于判断重复评测导致的评测执行错误 + if (TpCsts.REDO_EVALUATING_REDIS_VALUE_TRUE.equals(redoEvaluating)) { + JedisUtil.psetex(key, TpCsts.REDO_EVALUATING_REDIS_VALUE_TRUE, 5000L); + } else { + JedisUtil.del(key); + } + } + + public void recordResetLocal(String tpiID) { + String key = TpCsts.RESET_LOCAL_REDIS_KEY + tpiID; + JedisUtil.psetex(key, "true", 20000L); + } + + public boolean isResetLocalEva(String tpiID, Instant evaTime) { + if ((double) Duration.between(evaTime, Instant.now()).toMillis() / 1000 < 1) { + String key = TpCsts.RESET_LOCAL_REDIS_KEY + tpiID; + if (JedisUtil.exists(key)) { + JedisUtil.del(key); + return true; + } + } + return false; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/GitAccountService.java b/web/src/main/java/com/imitate/web/module/game/service/GitAccountService.java new file mode 100644 index 0000000..0e828ec --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/GitAccountService.java @@ -0,0 +1,55 @@ +package com.imitate.web.module.game.service; + +import com.imitate.common.bean.ShellResult; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.ShellUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * created by weishao at 2017/8/1 + */ +@Service +public class GitAccountService { + + @Autowired + private AppConfig appConfig; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public void gitCredentialStore(String gitIP, String protocol) throws BusinessException { + String cmd = "echo -e 'protocol=" + protocol + "\nhost=" + gitIP + "' | git credential-store get"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 3); + if (result.getExitStatus() != 0) { + logger.error("获取git凭证失败!gitIP: {} e:{}", gitIP, result); + throw new BusinessException(ErrorCodeEnum.GIT_FAIL.getValue(),ErrorCodeEnum.GIT_FAIL.getDescription() + gitIP); + } + String out = result.getOut(); + // 无凭证或者凭证用户密码不匹配则设置凭证 + String username = appConfig.getGitUsername(); + String password = appConfig.getGitPassword(); + logger.debug("获取git凭证信息: {}", out); + if (StringUtils.isEmpty(out) || !out.contains(username) || !out.contains(password)) { + String setCmd = "git config --global user.email educoder@163.com" + + " && git config --global user.name educoder" + " && git config --global credential.helper store" + + " && echo -e 'protocol=" + protocol + "\nhost=" + gitIP + "\nusername=" + username + "\npassword=" + + password + "' | git credential approve"; + setGitCredential(setCmd, gitIP); + } + } + + private void setGitCredential(String cmd, String gitIP) throws BusinessException { + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 3); + if (result.getExitStatus() != 0) { + logger.error("设置git凭证失败!gitIP: {} e:{} ", gitIP, result); + throw new BusinessException(ErrorCodeEnum.GIT_CREDENTIAL_FAIL.getValue(),ErrorCodeEnum.GIT_CREDENTIAL_FAIL.getDescription() + gitIP); + } + logger.info("设置git凭证成功, gitIP: {}", gitIP); + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/GitService.java b/web/src/main/java/com/imitate/web/module/game/service/GitService.java new file mode 100644 index 0000000..00df4af --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/GitService.java @@ -0,0 +1,358 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.bean.ApiResult; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.constant.ApiResultCsts; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.GameHelper; +import com.imitate.common.util.OKHttp3Utils3; +import com.imitate.common.util.ShellUtil; +import com.imitate.common.util.TpUtils; +import com.imitate.web.module.game.enums.GitUrlComponent; +import com.imitate.web.module.game.pojo.GitPullRequestParam; +import com.imitate.web.module.game.pojo.GitResetRequestParam; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * git服务 + */ +@Service +public class GitService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** + * http形式git地址正则 + */ + private static final Pattern HTTP_GIT_URL_PATTERN = Pattern.compile("^http(s)?:\\/\\/([^\\/]+?\\/){2}.*?.git$"); + + @Autowired + private GameService gameService; + + + + + @Autowired + private ClusterConfigService clusterConfigService; + + @Autowired + private AppConfig appConfig; + + @Autowired + private SysConfigService sysConfigService; + + @Autowired + private DiskService diskService; + + public Boolean remoteClusterPull(String cluster, Boolean isLocal, String tpiId, JSONObject buildParams) { + GitPullRequestParam requestParam = getGitPullParam(tpiId, buildParams); + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/pull"; + if (StringUtils.isEmpty(url)) { + return Boolean.FALSE; + } + url = url + "?t=" + System.currentTimeMillis(); + String param = JSONObject.toJSONString(requestParam); + logger.info("gitURL: {}, result: {}", url, param); + + ApiResult result = OKHttp3Utils3.sendPost(url, param); + logger.info("{} remote node pull result: {}", tpiId, result); + if (result.getCode() == ApiResultCsts.CODE_FAIL) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public Boolean remoteClusterJupyterPull(String cluster, Boolean isLocal, String tpiID, String gitUrl) { + String tpiRepoName = GameHelper.getGitRepoName(gitUrl); + GitPullRequestParam requestParam = getGitPullParam(tpiID, gitUrl, tpiRepoName); + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/jupyterPull"; + if (StringUtils.isEmpty(url)) { + return Boolean.FALSE; + } + url = url + "?t=" + System.currentTimeMillis(); + String param = JSONObject.toJSONString(requestParam); + logger.info("gitURL: {}, result: {}", url, param); + + ApiResult result = OKHttp3Utils3.sendPost(url, param); + logger.info("{} remote node jupyter pull result: {}", tpiID, result); + if (result.getCode() == ApiResultCsts.CODE_FAIL) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + private GitPullRequestParam getGitPullParam(String tpiID, String gitUrl, String tpiRepoName) { + GitPullRequestParam param = new GitPullRequestParam(); + String tpiWorkspace = diskService.buildTpiWorkspace(appConfig.getWorkspace(), tpiID); + + param.setTpiId(tpiID); + if (!isRemoteClusterGitUrl(gitUrl)) { + gitUrl = TpUtils.buildRemoteClusterGitURL(gitUrl, appConfig.getGitServiceDomain()); + } + param.setTpiGitURL(gitUrl); + param.setTpiWorkspace(tpiWorkspace); + param.setTpiRepoName(tpiRepoName); + param.setContentModified(0); + return param; + } + + public Boolean remoteClusterResetTpmRepository(String cluster, Boolean isLocal, String tpiId, GitResetRequestParam resetRequestParam) { + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/resetTpmRepository"; + if (StringUtils.isEmpty(url)) { + return Boolean.FALSE; + } + url = url + "?t=" + System.currentTimeMillis(); + String param = JSONObject.toJSONString(resetRequestParam); + logger.info("gitURL: {}, result: {}", url, param); + + ApiResult result = OKHttp3Utils3.sendPost(url, param); + logger.info("{} remote node resetTpmRepository result: {}", tpiId, result); + if (result.getCode() == ApiResultCsts.CODE_FAIL) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public void remoteClusterDeleteSecret(String cluster, Boolean isLocal, String secretRepospace) { + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/deleteSecret"; + if (StringUtils.isEmpty(url)) { + return; + } + url = url + "?t=" + System.currentTimeMillis(); + Map param = new HashMap<>(); + param.put("secretRepospace", secretRepospace); + + OKHttp3Utils3.sendPost(url, param); + logger.info("remoteClusterDeleteSecret: url: {}, param: {}, result: {}", url, param); + } + + private GitPullRequestParam getGitPullParam(String tpiId, JSONObject buildParams) { + GitPullRequestParam requestParam = new GitPullRequestParam(); + requestParam.setTpiId(tpiId); + + String tpiGitURL = buildParams.getString("tpiGitURL"); + if (!isRemoteClusterGitUrl(tpiGitURL)) { + tpiGitURL = TpUtils.buildRemoteClusterGitURL(tpiGitURL, appConfig.getGitServiceDomain()); + } + requestParam.setTpiGitURL(tpiGitURL); + + String tpiWorkspace = buildParams.getString("tpiWorkspace"); + requestParam.setTpiWorkspace(tpiWorkspace); + + String tpiRepoName = buildParams.getString("tpiRepoName"); + requestParam.setTpiRepoName(tpiRepoName); + + String tpiProtectspace = diskService.buildTpiProtectSpace(appConfig.getWorkspace(), tpiId); + requestParam.setTpiProtectspace(tpiProtectspace); + + String tpmScript = buildParams.getString("tpmScript"); + requestParam.setTpmScript(tpmScript); + + String secretDir = buildParams.getString("secretDir"); + requestParam.setSecretDir(secretDir); + + String secretGitUrl = buildParams.getString("secretGitUrl"); + if (StringUtils.isNotEmpty(secretGitUrl) && !isRemoteClusterGitUrl(secretGitUrl)) { + secretGitUrl = TpUtils.buildRemoteClusterGitURL(secretGitUrl, appConfig.getGitServiceDomain()); + } + requestParam.setSecretGitUrl(secretGitUrl); + + Integer contentModified = buildParams.getInteger("contentModified"); + requestParam.setContentModified(contentModified); + + requestParam.setFile(buildParams.getString("file")); + + return requestParam; + } + + /** + * 代码下载地址是否是远程git服务的地址 + * + * @param gitUrl + * @return + */ + public boolean isRemoteClusterGitUrl(String gitUrl) { + String remoteCusterGitHost = sysConfigService.getRemoteClusterGitHost(); + if (StringUtils.isNotEmpty(remoteCusterGitHost)) { + try { + URL url = new URL(gitUrl); + return remoteCusterGitHost.contains(url.getHost()); + } catch (MalformedURLException e) { + logger.error("URL 构造失败 gitUrl: {}", gitUrl, e); + } + } + return Boolean.FALSE; + } + + public Boolean remoteClusterCheckCode(String cluster, Boolean isLocal, String tpiId, String tpiGitURL) { + GitPullRequestParam requestParam = getGitPullParam(tpiId, tpiGitURL, TpCsts.TP_UNIFY_REPO_NAME); + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/checkCode"; + if (StringUtils.isEmpty(url)) { + return Boolean.FALSE; + } + url = url + "?t=" + System.currentTimeMillis(); + String param = JSONObject.toJSONString(requestParam); + logger.info("gitURL: {}, result: {}", url, param); + + ApiResult result = OKHttp3Utils3.sendPost(url, param); + logger.info("{} remote node checkCode result: {}", tpiId, result); + if (result.getCode() == ApiResultCsts.CODE_FAIL) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public Boolean remoteUpdateJupyterTpm(String cluster, String tpiID, Boolean isLocal, String content) { + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/updateJupyterTpm"; + if (StringUtils.isEmpty(url)) { + return Boolean.FALSE; + } + url = url + "?t=" + System.currentTimeMillis(); + JSONObject param = new JSONObject(); + param.put("tpiID", tpiID); + param.put("content", content); + String tpiWorkspace = diskService.buildTpiWorkspace(appConfig.getWorkspace(), tpiID); + param.put("tpiWorkspace", tpiWorkspace); + + ApiResult result = OKHttp3Utils3.sendPost(url, param.toJSONString()); + logger.info("{} remote node checkCode result: {}", tpiID, result); + if (result.getCode() == ApiResultCsts.CODE_FAIL) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + public void gitPushJupyterToTpi(String path, String tpiRepoName, String tpiGitURL, String tpiID) + throws BusinessException { + + // 从URL中取仓库名 + String tpiRepoPath = diskService.buildTpiRepoPath(path, tpiRepoName); + + // push之前,先判断origin主机是否存在,若不存在,则先创建 + gameService.gitAddRemote("origin", tpiRepoPath, tpiGitURL, tpiID); + + String cmd = "cd " + tpiRepoPath + // + " && echo ' ' >> 01.ipynb " // 让git 感知变化 + + " && git add 01.ipynb " + + " && git config user.email educoder@163.com && git config user.name educoder" + + " && git commit -m'reset jupyter'"; + ShellResult result = ShellUtil.executeAndGetExitStatus(cmd, 1); + if (result.getExitStatus() != 0) { + String msg = result.getOut(); + if (msg.contains("nothing to commit")) { // tpi相对于tpm没有任何更改,tpi打开后没有更新操作 + logger.info("gitPushToTpi: push成功,nothing to commit! tpiId [{}] cmd [{}]", tpiID, cmd); + return; + } else if (msg.contains("nothing added to commit but untracked files present")) {// tpi打开后有更新操作 + logger.info("gitPushToTpi: push成功,nothing added to commit! tpiId [{}] cmd [{}]", tpiID, cmd); + return; + } else { + logger.error("gitPushToTpi: add and commit失败, tpiId [{}] cmd [{}] error[{}]", tpiID, cmd, + result.getOut()); + throw new BusinessException(ErrorCodeEnum.JUPYTER_ADD_OR_COMMIT_FAIL.getValue(), "jupyter add and commit失败,版本库地址:" + tpiGitURL + " tpiID:" + tpiID); + } + } + + // 执行push -f (注意,push需要权限) + cmd = "cd " + tpiRepoPath + " && git remote set-url origin " + tpiGitURL + " && git push -f origin master"; + result = ShellUtil.executeAndGetExitStatus(cmd, 3); + if (result.getExitStatus() != 0) { + String msg = result.getOut(); + logger.error("gitPushToTpi: push失败, tpiId [{}] cmd [{}] error[{}]", tpiID, cmd, msg); + throw new BusinessException(ErrorCodeEnum.PUSH_FAIL.getValue(), "push失败,版本库地址:" + tpiGitURL + " tpiID:" + tpiID); + } else { + logger.debug("gitPushToTpi: push成功! tpiId [{}] cmd [{}]", tpiID, cmd); + } + + } + + public ApiResult remoteClusterOjWrite(String cluster, Boolean isLocal, String tpiID, String tpiWorkspace, String codeFileName, String codeFileContent) { + ApiResult result = new ApiResult<>(); + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/ojWrite"; + if (StringUtils.isEmpty(url)) { + result.setCode(ApiResultCsts.CODE_FAIL); + result.setMsg("remote cluster git url is empty"); + return result; + } + url = url + "?t=" + System.currentTimeMillis(); + JSONObject param = new JSONObject(); + param.put("tpiID", tpiID); + param.put("tpiWorkspace", tpiWorkspace); + param.put("codeFileName", codeFileName); + param.put("codeFileContent", codeFileContent); + logger.info("gitURL: {}, param: {}", url, param); + + result = OKHttp3Utils3.sendPost(url, param.toJSONString()); + logger.info("{} remote node pull result: {}", tpiID, result); + return result; + } + + public ApiResult remoteClusterOjCompile(String cluster, Boolean isLocal, String tpiID, String codeFilePath) { + ApiResult result = new ApiResult<>(); + String url = clusterConfigService.getGitUrl(cluster, isLocal) + "/ojCompile"; + if (StringUtils.isEmpty(url)) { + result.setCode(ApiResultCsts.CODE_FAIL); + result.setMsg("remote cluster git url is empty"); + return result; + } + url = url + "?t=" + System.currentTimeMillis(); + JSONObject param = new JSONObject(); + param.put("tpiID", tpiID); + param.put("codeFilePath", codeFilePath); + + logger.info("gitURL: {}, param: {}", url, param); + + result = OKHttp3Utils3.sendPost(url, param.toJSONString()); + logger.info("{} remote node pull result: {}", tpiID, result); + return result; + } + + /** + * 获取git url的组成部分 + * + * @param gitUrl http形式的git 地址 + * @param gitUrlComponent 组成部分 + * @return git url的组成部分 + */ + public String getGitUrlComponent(String gitUrl, GitUrlComponent gitUrlComponent) { + String result = StringUtils.EMPTY; + + Matcher matcher = HTTP_GIT_URL_PATTERN.matcher(gitUrl); + if (matcher.find()) { + switch (gitUrlComponent) { + case HOST: + result = gitUrl.substring(gitUrl.indexOf("/") + 2, gitUrl.indexOf("/", gitUrl.indexOf("/") + 2)); + break; + case PROTOCOL: + result = gitUrl.substring(0, gitUrl.indexOf(":")); + break; + case PATH: + String tmp = gitUrl.substring(gitUrl.indexOf("/") + 2); + result = tmp.substring(tmp.indexOf("/") + 1, tmp.lastIndexOf(".")); + break; + default: + break; + } + } + + return result; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/ImageService.java b/web/src/main/java/com/imitate/web/module/game/service/ImageService.java new file mode 100644 index 0000000..58de125 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/ImageService.java @@ -0,0 +1,90 @@ +package com.imitate.web.module.game.service; + +import com.aliyuncs.CommonRequest; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.http.FormatType; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.profile.DefaultProfile; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.ShellUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 镜像服务 + */ +@Service("imageService") +public class ImageService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public static final String IMAGE_TYPE_WINDWS = "windows"; + + public static final String IMAGE_TYPE_WEBSSH = "webssh"; + + public static final String IMAGE_TYPE_VNC = "vnc"; + + + + @Autowired + private SysConfigService sysConfigService; + + + + + + public boolean createDockerImage(String tpiID, String podName, String imageName, String nodeIp) { + String remoteConnection = String.format(sysConfigService.getRemoteConnPrefix(), nodeIp); + String getCommand = remoteConnection + " \" docker ps | grep _"+ podName +"_ | grep -v k8s_POD | cut -c -12 \""; + ShellResult result = ShellUtil.executeAndGetExitStatus(getCommand); + if (0 == result.getExitStatus()) { + String containerId = result.getOut(); + String createCommand = remoteConnection + " \"docker commit "+ containerId +" "+ imageName +" && push.sh "+ imageName +" && docker rmi "+ imageName +"\""; + result = ShellUtil.executeAndGetExitStatus(createCommand); + if (0 == result.getExitStatus()) { + // 记录创建镜像名与时间,30天后定时删除 + String createTime = String.valueOf(System.currentTimeMillis()); + JedisUtil.hset(TpCsts.CREATE_IMAGE_MAP, imageName, createTime); + + // 记录已创建镜像tpiID和镜像名,创建实验环境时直接按照该镜像名创建 + JedisUtil.psetex(TpCsts.CREATE_IMAGE_NAME_REDIS_KEY + tpiID, imageName, TpCsts.CREATE_IMAGE_NAME_REDIS_EXPIRE_TIME); + return true; + } else { + logger.error("创建镜像失败 tpiID: {}, command: {}, error: {}", tpiID, createCommand, result.getOut()); + } + } + return false; + } + + public void deleteRepoImage(String imageName) { + DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI4GHNeBq8S46xLXDgeXKg", "RzbHOFQ2YD1Al88MxlykYO1HDGWmb6"); + IAcsClient client = new DefaultAcsClient(profile); + + CommonRequest request = new CommonRequest(); + request.setMethod(MethodType.DELETE); + request.setDomain("cr.cn-hangzhou.aliyuncs.com"); + request.setVersion("2016-06-07"); + request.setUriPattern("/repos/educoder/educoder/tags/python3-test"); + + request.putHeadParameter("Content-Type", "application/json"); + String requestBody = "" + + "{}"; + request.setHttpContent(requestBody.getBytes(), "utf-8", FormatType.JSON); + try { + client.getCommonResponse(request); + } catch (ClientException e) { + logger.error("删除远程仓库镜像失败 imageName: {}", imageName, e); + e.printStackTrace(); + } + } + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/KubernetesService.java b/web/src/main/java/com/imitate/web/module/game/service/KubernetesService.java new file mode 100644 index 0000000..0293de5 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/KubernetesService.java @@ -0,0 +1,264 @@ + +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.bean.ShellResult; +import com.imitate.common.constant.BuildResultCsts; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.bean.BuildResult; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.pojo.BridgePod; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.*; +import com.imitate.web.module.game.pojo.TimeoutResult; +import io.fabric8.kubernetes.api.model.Node; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Instant; + +/** + * 与kubernetes交互的service + * + * @author weishao + * @date 2017/11/05 + */ +@Service +public class KubernetesService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private K8sService k8sService; + @Autowired + private GameService gameService; + @Autowired + private RunPodService runPodService; + @Autowired + private ClusterConfigService clusterConfigService; + @Autowired + private GitService gitService; + @Autowired + private ClusterManager clusterManager; + @Autowired + private EvaTimeoutService evaTimeoutService; + @Autowired + private ShellExeCallBack shellExeCallBack; + @Autowired + private TestCaseService testCaseService; + @Autowired + private AppConfig appConfig; + @Autowired + private DiskService diskService; + + + /** + * 在pod中执行评测脚本 + */ + public void executeShellInPod(final String cluster, JSONObject buildParams, Pod pod, int timeLimit, + int isPublished, int instanceChallenge, JSONArray cases) { + String podName = pod.getMetadata().getName(); + String nodeName = pod.getSpec().getNodeName(); + String tpiID = buildParams.getString("tpiID"); + String secretRepospace = buildParams.getString("secretRepospace"); + String input = testCaseService.getInputRemoveIdentifier(cases, buildParams.getString("tpmIdentifier")); + String type = testCaseService.getType(cases); + //每个宿主机 + String shFile = TpCsts.TPI_PLATFORM_MOUNT_PATH + "/execEva.sh"; + boolean isLocalCluster = clusterManager.isLocalCluster(cluster); + boolean isLocal = clusterManager.isLocalNode(cluster, nodeName); + String fileTypeActualOutMountPath = diskService.buildTpiTestCaseActualOutMountPath(appConfig.getWorkspace()); + String fileTypeActualOutMountHostPath = diskService.getTpiTestCaseActualOutHostPath(tpiID); + buildParams.put("podName", podName); + + String evaluateStartTime = buildParams.getString("evaluateStartTime"); + int timeout = TimeCostUtils.getRemainingTime(evaluateStartTime, timeLimit); + timeout = Math.min(timeout, timeLimit); + if (timeout <= 0) { + String tip = BuildResultCsts.EVA_RETRY_ERROR_BASE64; + buildParams.put("evaFailStatus", BridgePod.RUN_STATUS_EXEC_FAIL); + String msg = GameHelper.getErrorResult(cases.size(), tip); + gameService.encapsulateBuildResult(cluster, msg, buildParams, cases); + if (StringUtils.isNotEmpty(secretRepospace)) { + if (clusterManager.isLocalCluster(cluster)) { + gameService.cleanSecretFileContent(secretRepospace); + } else { + gitService.remoteClusterDeleteSecret(cluster, Boolean.TRUE, secretRepospace); + } + } + return; + } + + + String command = "timeout " + (timeout + 1) + " kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " exec " + podName + + " -- bash " + shFile + " " + timeout + " " + instanceChallenge + " " + input + " " + type + " " + fileTypeActualOutMountPath + " " + fileTypeActualOutMountHostPath; + logger.info("执行评测, pod: {}, node: {}, command: {}", podName, nodeName, command); + + Instant executeStartInstant = Instant.now(); + ShellResult buildResult = ShellUtil.executeAndGetExitStatus(command, shellExeCallBack, buildParams); + if (gameService.isResetLocalEva(buildParams.getString("tpiID"), executeStartInstant)) { + logger.info("重置本地版本后马上评测将会出现无效文件符导致评测失败,重新执行评测 podName: {}", podName); + buildResult = ShellUtil.executeAndGetExitStatus(command, shellExeCallBack, buildParams); + } + int exitStatus = buildResult.getExitStatus(); + String out = buildResult.getOut(); + String executeTime = TimeCostUtils.getStepTimeCost(executeStartInstant); + buildParams.put("execute", executeTime); + + BuildResult result = processResult(cluster, buildParams, pod, timeLimit, timeout, isPublished, + cases, command, exitStatus, out, Double.parseDouble(executeTime)); + if(result == null) { + return; + } + if (StringUtils.isNotEmpty(secretRepospace)) { + if (isLocalCluster) { + gameService.cleanSecretFileContent(secretRepospace); + } else { + gitService.remoteClusterDeleteSecret(cluster, isLocal, secretRepospace); + } + } + + // pod 回收 + boolean taskResult = BuildResult.Status.PASS.getValue().equals(result.getStatus()); + Node node = clusterManager.getNode(cluster, nodeName); + if (taskResult || K8sUtils.isOjNode(node)) { + if (podName.startsWith("evaluate") && !svcExist(cluster, podName) && !svcExist(cluster, podName + "-22")) { + runPodService.deleteRunPodAfterTime(podName, appConfig.getEvaluatePodPassDelayDeleteTime()); + } + } + } + + + /** + * 在pod中执行Python评测脚本 + */ + public void executePythonInPod(final String cluster, JSONObject buildParams, Pod pod, int timeLimit, + int isPublished, JSONArray cases) { + String podName = pod.getMetadata().getName(); + String nodeName = pod.getSpec().getNodeName(); + String input = testCaseService.getInputRemoveIdentifier(cases, buildParams.getString("tpmIdentifier")); + String shFile = TpCsts.TPI_PLATFORM_MOUNT_PATH + "/evaluate.sh"; + String sourceFile = diskService.buildTpiWorkspace(appConfig.getWorkspace(), buildParams.getString("tpiID")); + buildParams.put("podName", podName); + + String evaluateStartTime = buildParams.getString("evaluateStartTime"); + int timeout = TimeCostUtils.getRemainingTime(evaluateStartTime, timeLimit); + timeout = Math.min(timeout, timeLimit); + if (timeout <= 0) { + String tip = BuildResultCsts.EVA_RETRY_ERROR_BASE64; + buildParams.put("evaFailStatus", BridgePod.RUN_STATUS_EXEC_FAIL); + String msg = GameHelper.getErrorResult(cases.size(), tip); + gameService.encapsulateBuildResult(cluster, msg, buildParams, cases); + return; + } + + //执行脚本 + String command = "timeout " + (timeout + 1) + " kubectl --kubeconfig=" + clusterConfigService.getKubeConfig(cluster) + " exec " + podName + + " -- bash " + shFile + " 1 " + input + " " + sourceFile + "/jupyter.py"; + logger.info("执行评测, pod: {}, node: {}, command: {}", podName, nodeName, command); + + Instant executeStartInstant = Instant.now(); + //执行代码,获取结果 + ShellResult buildResult = ShellUtil.executeAndGetErrorStatus(command,shellExeCallBack, buildParams); + Integer exitStatus = buildResult.getExitStatus(); + String executeTime = TimeCostUtils.getStepTimeCost(executeStartInstant); + buildParams.put("execute", executeTime); + + BuildResult result = processResult(cluster, buildParams, pod, timeLimit, timeout, isPublished, + cases, command, exitStatus, buildResult.getOut(), Double.parseDouble(executeTime)); + if(result == null) { + return; + } + + + // pod 回收 + Node node = clusterManager.getNode(cluster, nodeName); + if (BuildResult.Status.PASS.getValue().equals(result.getStatus()) || K8sUtils.isOjNode(node)) { + if (podName.startsWith("evaluate") && !svcExist(cluster, podName) && !svcExist(cluster, podName + "-22")) { + runPodService.deleteRunPodAfterTime(podName, appConfig.getEvaluatePodPassDelayDeleteTime()); + } + } + + + } + + + + public BuildResult processResult(String cluster, JSONObject buildParams, Pod pod, int timeLimit, int timeout, int isPublished, + JSONArray cases, String command, int exitStatus, String out, Double executeTime) { + String podName = pod.getMetadata().getName(); + String nodeName = pod.getSpec().getNodeName(); + + if (executeTime >= timeout-1 && evaTimeoutService.isTimeout(exitStatus, out)) { + String tpiID = buildParams.getString("tpiID"); + TimeoutResult timeoutResult = evaTimeoutService.timeoutResultAnalysis(cluster, podName, nodeName, out, cases.size(), timeLimit, timeout, isPublished, tpiID); + buildParams.put("timeoutCode", timeoutResult.getTimeoutCode()); // TODO 不应放在这里 + buildParams.put("evaFailStatus", BridgePod.RUN_STATUS_TIMEOUT); + out = timeoutResult.getOut(); + logger.error("评测超时 tpiID: {}", tpiID); + } + String oriOut = out; + // 为了避免其他输出干扰结果判断,仅提取平台封装输出 + out = GameHelper.getRealResult(out); + if (isPublished == 1) {// 已经发布 + // 如果结果为空或者不是有效的json格式,报系统繁忙 + if (StringUtils.isEmpty(out) || !GameHelper.isValidJSON(out)) { + String tip = BuildResultCsts.EVA_RETRY_ERROR_BASE64; + if (StringUtils.isEmpty(oriOut)) { + tip = BuildResultCsts.EVA_PUBLISHED_SCRIPT_ERROR_BASE64; + } else if (oriOut.contains("Error from server (NotFound): pods")) { + tip = BuildResultCsts.POD_HAVE_BEEN_CLEANED_BASE64; + } else if (gameService.isRedoEvaluating(buildParams.getString("tpiID"))) { + tip = BuildResultCsts.REDO_EVALUATING_FAIL_BASE64; + } else if (evaTimeoutService.isOverMemoryLimit(buildParams, oriOut)) { + tip = BuildResultCsts.EVA_OVER_MEMORY_LIMIT_BASE64; + } else { + logger.error("系统繁忙,执行命令:{}, 原始输出:{}, 最终输出: {}, nodeName: {}", command, oriOut, out, nodeName); + if ((oriOut.startsWith("OCI runtime exec failed: exec failed") + || oriOut.startsWith("Error from server: error dialing backend") + || oriOut.startsWith("Defaulting container name to") + || oriOut.contains("unable to upgrade connection")) + && StringUtils.isEmpty(buildParams.getString("k8sRedoExec"))) { + buildParams.put("k8sRedoExec", "true"); + ThreadUtils.sleep(2000); + executeShellInPod(cluster, buildParams, pod, timeLimit, isPublished, buildParams.getInteger("instanceChallenge"), cases); + return null; + } + buildParams.put("evaFailStatus", BridgePod.RUN_STATUS_EXEC_FAIL); + } + out = GameHelper.getErrorResult(cases.size(), tip); + } + } else {// 未发布(调试模式) + // 如果结果为空或者不是有效的json格式,可判定为脚本错误 + if (StringUtils.isEmpty(out) || !GameHelper.isValidJSON(out)) { + out = GameHelper.getShellErrorResult(cases.size(), oriOut); + } else { // 否则为正常输出 + if (!out.equals(oriOut)) { // 对于正常输出,如果没有任何冗余,不予提示,否则提示为调试模式 + JSONObject buildOutAndOriOut = JSONObject.parseObject(out); + buildOutAndOriOut.put("oriOut", oriOut); + out = buildOutAndOriOut.toString(); + } + } + } + return gameService.encapsulateBuildResult(cluster, out, buildParams, cases); + } + + /** + * 需要端口映射服务的pod,判断其svc是否存在 + * + * @param podName + * @return + */ + public boolean svcExist(final String cluster, String podName) { + return k8sService.svcExist(cluster, podName); + } +} \ No newline at end of file diff --git a/web/src/main/java/com/imitate/web/module/game/service/MqttCallBackService.java b/web/src/main/java/com/imitate/web/module/game/service/MqttCallBackService.java new file mode 100644 index 0000000..557a930 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/MqttCallBackService.java @@ -0,0 +1,49 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.util.JedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttMessage; + + +/** + * 订阅与发布主题的回调类 + */ +@Slf4j +public class MqttCallBackService implements MqttCallback { + + @Override + public void connectionLost(Throwable throwable) { + //连接断掉会执行到这里 + log.info("连接已断,请重新连接!!!"); + } + + @Override + public void messageArrived(String s, MqttMessage mqttMessage) throws Exception { + //subscribe后会执行到这里 + log.info("消息的主题是:{}",s); + log.info("消息的Qos是:{}",mqttMessage.getQos()); + log.info("消息的ID是:{}",mqttMessage.getId()); + log.info("树莓派发来结果:{}",new String(mqttMessage.getPayload())); + String result = new String(mqttMessage.getPayload()); + if(!"close".equals(result) && "result".equals(s)){ + JSONObject rst = JSON.parseObject(result); + String tpiID = rst.getString("tpiID"); + String key = "mqtt_" + tpiID + "_rst"; + //结果保存到redis + JedisUtil.psetex(key,result,18000L); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + //publish可以执行到这里 + log.info("发布消息状态:"+iMqttDeliveryToken.isComplete()); + + } + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/ShellExeCallBackService.java b/web/src/main/java/com/imitate/web/module/game/service/ShellExeCallBackService.java new file mode 100644 index 0000000..14a0b1e --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/ShellExeCallBackService.java @@ -0,0 +1,41 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.JsonUtils; +import com.imitate.common.util.ShellExeCallBack; +import com.imitate.web.module.game.pojo.EvaStepOut; +import com.imitate.web.module.game.pojo.SimpleBuildResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author mumu + */ +@Service +public class ShellExeCallBackService implements ShellExeCallBack { + + + @Autowired + AppConfig appConfig; + + @Autowired + private GameService gameService; + + @Override + public boolean processLine(String line, JSONObject param) { + if (!line.startsWith(TpCsts.EVA_STEP_OUT_MSG_PREFIX) || !line.endsWith(TpCsts.EVA_STEP_OUT_MSG_SUFFIX)) { + return false; + } + + EvaStepOut evaStepOut = JsonUtils.toBean(line, EvaStepOut.class); + + String buildID = param.getString("buildID"); + String simpleCallBackUrl = param.getString("simpleCallBackUrl"); + gameService.encapsulateSimpleBuildResult(simpleCallBackUrl, evaStepOut.getStep(), SimpleBuildResult.STATUS_STEP_OUT, + evaStepOut.getRatio(), buildID); + return true; + } + +} \ No newline at end of file diff --git a/web/src/main/java/com/imitate/web/module/game/service/TestCaseService.java b/web/src/main/java/com/imitate/web/module/game/service/TestCaseService.java new file mode 100644 index 0000000..f5d96a3 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/TestCaseService.java @@ -0,0 +1,123 @@ +package com.imitate.web.module.game.service; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.Joiner; +import com.imitate.common.util.Base64Util; +import com.imitate.common.util.FileUtil; +import com.imitate.web.module.game.pojo.ExecSourceCase; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 测试集相关业务实现 + * + * @author 威少 + */ +@Service +@Slf4j +public class TestCaseService { + private static final String COMMA = ","; + + /** + * 从测试集提取符合shell格式的输入 + * + * @param cases 测试集 + * @return shell测试集输入,形如 "IA==","Mg==" + */ + public String getInput(JSONArray cases) { + return getElementBase64NoSafe(cases, "input"); + } + + /** + * 从测试集提取符合shell格式的输入,去除substr + * + * @param cases 测试用例 + * @param identifier 标识 + * @return 剔除子串后,base64编码,以,分隔拼成序列,形如 "IA==","Mg==" + */ + public String getInputRemoveIdentifier(JSONArray cases, String identifier) { + List base64EncodedInput = new ArrayList<>(); + + // 替换末尾换行符 + convertLineSeparatorToUnixEOL(cases); + + // 去除identifier + cases.forEach((c) -> { + String input = ((JSONObject) c).getString("input"); + if (ExecSourceCase.Type.FILE.getValue().equals(((JSONObject) c).getString("type"))) { + input = input.replace(identifier == null ? "" : identifier + "/", ""); + } + base64EncodedInput.add(Base64Util.encodeNoSafe(input)); + }); + + return Joiner.on(COMMA).join(base64EncodedInput); + } + + /** + * 从测试集提取符合shell格式的类型 + * + * @param cases 测试集 + * @return shell测试集类型输入,形如 "0","1" + */ + public String getType(JSONArray cases) { + return Joiner.on(COMMA).useForNull("0").join(getElement(cases, "type")); + } + + /** + * 从测试集取得元素,base64编码,以","分隔 + * + * @param cases 测试集 + * @param eleKey 数组元素key值 + * @return base64编码后以逗号分隔的字符串序列 + */ + private String getElementBase64NoSafe(JSONArray cases, String eleKey) { + convertLineSeparatorToUnixEOL(cases); + return Joiner.on(COMMA).join(cases.stream().map(o -> Base64Util.encodeNoSafe(((JSONObject) o).getString(eleKey))).collect(Collectors.toList())); + } + + /** + * 文件换行符更改为Unix格式 + * @param cases 测试集 + */ + private void convertLineSeparatorToUnixEOL(JSONArray cases) { + cases.forEach((c) -> { + if (ExecSourceCase.Type.FILE.getValue().equals(((JSONObject) c).getString("type"))) { + try { + // 文件类型转换末尾换行符 + FileUtil.convertToUnixEOL(new File(((JSONObject) c).getString("input"))); + } catch (IOException e) { + log.error("convert test case input file to unix style line separator failed", e); + } + } + }); + } + /** + * 从测试集取得元素,以","分隔 + * + * @param cases 测试集 + * @param eleKey 数组元素key值 + * @return 元素列表 + */ + private List getElement(JSONArray cases, String eleKey) { + return cases.stream().map(o -> ((JSONObject) o).getString(eleKey)).collect(Collectors.toList()); + } + + + /** + * 测试用例中是否包含文件类型用例 + * + * @param cases 测试用例 + * @return 是否包含文件用例 + */ + public boolean containsFileCase(JSONArray cases) { + return cases.stream().anyMatch(c -> ExecSourceCase.Type.FILE.getValue().equals(((JSONObject) c).getString("type"))); + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/service/VscodeService.java b/web/src/main/java/com/imitate/web/module/game/service/VscodeService.java new file mode 100644 index 0000000..31ea5ef --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/service/VscodeService.java @@ -0,0 +1,150 @@ +package com.imitate.web.module.game.service; + + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.pojo.RunPod; +import com.imitate.common.k8s.service.K8sService; +import com.imitate.common.k8s.service.PortService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.K8sUtils; +import com.imitate.common.sys.constant.SysConfigCsts; +import com.imitate.common.sys.service.ClusterConfigService; +import com.imitate.common.sys.service.SysConfigService; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.TpUtils; +import io.fabric8.kubernetes.api.model.Pod; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +/** + * vscode + * @author yanchao + */ +@Service("vscodeService") +public class VscodeService { + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private K8sService k8sService; + @Autowired + private AppConfig appConfig; + @Autowired + private PortService portService; + @Autowired + private RunPodService runPodService; + @Autowired + private ClusterConfigService clusterConfigService; + @Autowired + private ClusterManager clusterManager; + @Autowired + private SysConfigService sysConfigService; + + /** + * 获取服务 + */ + private String getServicePort(final String cluster, String tpiID, Integer podType, Integer port) { + String podName = TpUtils.buildVscodePodName(tpiID, podType); + String serviceName = podName + "-" + port; + + // 查询 + io.fabric8.kubernetes.api.model.Service service = k8sService.getService(cluster, serviceName); + + // 查询不到创建 + if (service == null) { + String svcPort = portService.allocatePort(SysConfigCsts.KEY_USABLE_PORT_K8S) + ""; + k8sService.createService(cluster, serviceName, podName, tpiID, port, Integer.parseInt(svcPort)); + if (port == 8080) { + runPodService.updateRunPodSshPort(podName, svcPort); + } + return svcPort; + } else { + return String.valueOf(K8sUtils.getNodePort(service)); + } + + } + + public String getVscode(String cluster, String tpiID, Integer podType, String containers, String bigDataFile, Integer survivalSecond, Boolean createImage, Integer needPortMapping) throws InterruptedException { + // 获取pod vscode的工作目录 /data/workspace 重做镜像 默认command + Pod pod = getVscodePracticePod(cluster,tpiID, podType, containers, bigDataFile, survivalSecond, createImage); + + // 获取vscode service + String vscodePort = getServicePort(cluster, tpiID, podType, 8080); + String targetUrl = "https://" + vscodePort + appConfig.getVscodeDomain() + "?folder=" + appConfig.getWorkspace() + "/myshixun"; + + //等待5秒让容器的codeserver启动 + Thread.sleep(5000); + return targetUrl; + } + + /** + * 获取vscode的Pod + * @param tpiID + * @param podType + * @param containers + * @param bigDataFile + * @param survivalSecond + * @param createImage + * @return + */ + private Pod getVscodePracticePod(String cluster, String tpiID, Integer podType, String containers, String bigDataFile, Integer survivalSecond, Boolean createImage) { + // 如果存在pod,重置pod过期时间 + String podName = TpUtils.buildVscodePodName(tpiID, podType); + int delaySecond = (createImage && survivalSecond != null) ? survivalSecond : sysConfigService.getVscodePodDelaSecond(); + runPodService.deleteRunPodAfterTime(podName, delaySecond); + + // 检查pod是否存在,不存在新建 + boolean existPod = false; + Pod pod = k8sService.getPod(cluster, podName); + if (pod != null) { + String phase = pod.getStatus().getPhase(); + if ("Running".equals(phase)) { + existPod = true; + } else { + runPodService.deteleRunPodSync(cluster, tpiID, podName, podType); + logger.warn("获取vscode连接信息,delete 处于{}状态的 pod {}", phase, podName); + } + } + + + + if (!existPod) { + pod = runPodService.createVscodePod(cluster, podName, tpiID, TpUtils.cvtEvaPodType(podType), containers, podType, delaySecond, + bigDataFile, createImage); + } + + return pod; + } + + /** + * 直接删除 + * + * @param tpiID + * @throws Exception + */ + public void deleteNow(String tpiID) { + String podName = TpUtils.buildEvaPodName(tpiID, TpCsts.POD_TYPE_VSCODE); + RunPod runPod = runPodService.getRunPodByName(podName); + if (runPod == null) { + return; + } + + String cluster = runPod.getCluster(); + this.runPodService.deteleRunPodSync(cluster, tpiID, podName, TpCsts.POD_TYPE_VSCODE); + } + + public void active(String tpiID) { + String podName = TpCsts.TYPE_VSCODE + "-" + tpiID; + RunPod runPod = runPodService.getRunPodByName(podName); + if(runPod == null) { + logger.info("激活vscode pod, 但pod已经不存在,tpiID: {}", tpiID); + return; + } + + int delaySecond = sysConfigService.getVscodePodDelaSecond(); + runPodService.deleteRunPodAfterTime(runPod.getName(), delaySecond); + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolExecutor.java b/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolExecutor.java new file mode 100644 index 0000000..362fb3c --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolExecutor.java @@ -0,0 +1,89 @@ +package com.imitate.web.module.game.thread; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.BuildResultCsts; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.k8s.pojo.BridgePod; +import com.imitate.common.bean.BeanFactory; +import com.imitate.common.util.GameHelper; +import com.imitate.web.module.game.service.GameService; +import com.imitate.web.module.game.service.GitService; +import com.imitate.web.module.game.thread.eva.EvaJob; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; + +public class BridgeThreadPoolExecutor extends ThreadPoolExecutor { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + public BridgeThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); + + } + + @Override + protected void afterExecute(Runnable r, Throwable t) { + if (t == null) { + return; + } + + if (r instanceof EvaJob) { + processBuildThread(r, t); + } else if (r instanceof LogEnhancementRunnable) { + processLogEnhancementRunnable(r, t); + } else { + logger.error("未知任务", t); + } + } + + private void processBuildThread(Runnable r, Throwable t) { + JSONObject buildParams = null; + try { + EvaJob evaJob = (EvaJob) r; + buildParams = evaJob.getBuildParams(); + + String tpiID = buildParams.getString("tpiID"); + logger.error("评测线程" + tpiID + "出错", t); + + + JSONArray cases = JSONObject.parseArray(buildParams.getString("testCases")); + // 回传错误信息到前台 + final String cluster = buildParams.getString("cluster"); + GameService gameService = BeanFactory.getObejct(GameService.class); + buildParams.put("evaFailStatus", BridgePod.RUN_STATUS_OTHERS_FAIL); + gameService.encapsulateBuildResult(cluster, + GameHelper.getErrorResult(cases.size(), BuildResultCsts.EVA_UNDEFINED_ERROR_BASE64), buildParams, cases); + } catch (Throwable e) { + throw new BusinessException(ErrorCodeEnum.AFTERMATH_EXP.getValue(), ErrorCodeEnum.AFTERMATH_EXP.getDescription()); + } finally { + GameService gameService = BeanFactory.getObejct(GameService.class); + gameService.clearRedoEvaluating(buildParams.getString("tpiID")); + + String secretRepospace = buildParams.getString("secretRepospace"); + if (StringUtils.isNotEmpty(secretRepospace)) { + Boolean isLocal = buildParams.get("isLocal") == null ? Boolean.FALSE + : buildParams.getBoolean("isLocal"); + if (isLocal) { + gameService.cleanSecretFileContent(secretRepospace); + } else { + GitService gitService = BeanFactory.getObejct(GitService.class); + String cluster = buildParams.getString("cluster"); + gitService.remoteClusterDeleteSecret(cluster, isLocal, secretRepospace); + } + } + + } + } + + private void processLogEnhancementRunnable(Runnable r, Throwable t) { + LogEnhancementRunnable logRunnable = (LogEnhancementRunnable) r; + processBuildThread(logRunnable.getRunnable(), t); + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolTaskExecutor.java b/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolTaskExecutor.java new file mode 100644 index 0000000..c3f56b8 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/BridgeThreadPoolTaskExecutor.java @@ -0,0 +1,98 @@ +package com.imitate.web.module.game.thread; + +import com.imitate.common.bean.BeanFactory; +import com.imitate.common.config.UncaughtBridgeExceptionHandler; +import org.slf4j.MDC; +import org.springframework.core.task.TaskDecorator; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.*; + +@SuppressWarnings("serial") +public class BridgeThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { + + private int queueCapacity = Integer.MAX_VALUE; + + private TaskDecorator taskDecorator; + + private boolean allowCoreThreadTimeOut = false; + + /** + * 所有线程都会委托给这个execute方法,在这个方法中我们把父线程的MDC内容赋值给子线程 + * https://logback.qos.ch/manual/mdc.html#managedThreads + * + * @param runnable + */ + @Override + public void execute(Runnable runnable) { + // 获取父线程MDC中的内容,必须在run方法之前,否则等异步线程执行的时候有可能MDC里面的值已经被清空了,这个时候就会返回null + Map context = MDC.getCopyOfContextMap(); + LogEnhancementRunnable logEnhancementRunnable = new LogEnhancementRunnable(runnable, context); + super.execute(logEnhancementRunnable); + } + + + @Override + public Thread newThread(Runnable runnable) { + Thread t = super.newThread(runnable); + UncaughtBridgeExceptionHandler eh = BeanFactory.getObejct(UncaughtBridgeExceptionHandler.class); + t.setUncaughtExceptionHandler(eh); + return t; + } + + @Override + protected ExecutorService initializeExecutor(ThreadFactory threadFactory, + RejectedExecutionHandler rejectedExecutionHandler) { + BlockingQueue queue = createQueue(this.queueCapacity); + + ThreadPoolExecutor executor; + if (this.taskDecorator != null) { + // 使用 BridgeThreadPoolExecutor 执行 afterExecute, 处理异常情况 + executor = new BridgeThreadPoolExecutor(super.getCorePoolSize(), super.getMaxPoolSize(), + super.getKeepAliveSeconds(), TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) { + @Override + public void execute(Runnable command) { + super.execute(taskDecorator.decorate(command)); + } + }; + } else { + // 使用 BridgeThreadPoolExecutor 执行 afterExecute, 处理异常情况 + executor = new BridgeThreadPoolExecutor(super.getCorePoolSize(), super.getMaxPoolSize(), + super.getKeepAliveSeconds(), TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); + + } + + if (this.allowCoreThreadTimeOut) { + executor.allowCoreThreadTimeOut(true); + } + + Field field = ReflectionUtils.findField(ThreadPoolTaskExecutor.class, "threadPoolExecutor", + ThreadPoolExecutor.class); + field.setAccessible(true); + ReflectionUtils.setField(field, this, executor); + return executor; + } + + @Override + public void setQueueCapacity(int queueCapacity) { + super.setQueueCapacity(queueCapacity); + this.queueCapacity = queueCapacity; + + } + + @Override + public void setTaskDecorator(TaskDecorator taskDecorator) { + super.setTaskDecorator(taskDecorator); + this.taskDecorator = taskDecorator; + } + + @Override + public void setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) { + super.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut); + this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/LogEnhancementRunnable.java b/web/src/main/java/com/imitate/web/module/game/thread/LogEnhancementRunnable.java new file mode 100644 index 0000000..e1d1281 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/LogEnhancementRunnable.java @@ -0,0 +1,41 @@ +package com.imitate.web.module.game.thread; + +import com.alibaba.fastjson.JSONObject; +import com.imitate.web.module.game.thread.eva.EvaJob; +import org.slf4j.MDC; + +import java.util.Map; + +public class LogEnhancementRunnable implements Runnable { + private Runnable runnable; + private Map mdcContext; + + public LogEnhancementRunnable(Runnable runnable, Map mdcContext) { + super(); + this.runnable = runnable; + this.mdcContext = mdcContext; + } + + @Override + public void run() { + // 将父线程的MDC内容传给子线程 + EvaJob evaJob = (EvaJob) runnable; + JSONObject params = evaJob.getBuildParams(); + String cluster = "cluster"; + MDC.setContextMap(mdcContext); + MDC.put(cluster, params.getString(cluster)); + try { + // 执行异步操作 + runnable.run(); + } finally { + // 清空MDC内容 + MDC.clear(); + } + + } + + public Runnable getRunnable() { + return runnable; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/code/LocalCheckJob.java b/web/src/main/java/com/imitate/web/module/game/thread/code/LocalCheckJob.java new file mode 100644 index 0000000..47b578b --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/code/LocalCheckJob.java @@ -0,0 +1,44 @@ +package com.imitate.web.module.game.thread.code; + +import com.imitate.web.module.game.pojo.LocalCheck; +import com.imitate.web.module.game.service.GameService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +@Service() +@Scope("prototype") +public class LocalCheckJob implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(LocalCheckJob.class); + + @Autowired + private GameService gameService; + + private LocalCheck localCheck; + + @Override + public void run() { + + String tpiID = localCheck.getTpiID(); + String tpiGitUrl = localCheck.getTpiGitURL(); + String tpiWorkspace = localCheck.getTpiWorkSpace(); + try { + logger.info("[start job]local check tpi {}代码,tpiGitUrl {}", tpiID, tpiGitUrl); + gameService.checkLocalCode(tpiID, tpiGitUrl, tpiWorkspace); + logger.info("[end job]local check tpi {}代码,tpiGitUrl {}", tpiID, tpiGitUrl); + } catch (Throwable e) { + logger.error("[error job]local check tpi " + tpiID + "出错", e); + } + } + + public LocalCheck getLocalCheck() { + return localCheck; + } + + public void setLocalCheck(LocalCheck localCheck) { + this.localCheck = localCheck; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/eva/EvaJob.java b/web/src/main/java/com/imitate/web/module/game/thread/eva/EvaJob.java new file mode 100644 index 0000000..b79321f --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/eva/EvaJob.java @@ -0,0 +1,17 @@ +package com.imitate.web.module.game.thread.eva; + +import com.alibaba.fastjson.JSONObject; + +public abstract class EvaJob implements Runnable { + + protected JSONObject buildParams; + + public void setBuildParams(JSONObject buildParams) { + this.buildParams = buildParams; + } + + public JSONObject getBuildParams() { + return buildParams; + } + +} diff --git a/web/src/main/java/com/imitate/web/module/game/thread/eva/TpEvaJob.java b/web/src/main/java/com/imitate/web/module/game/thread/eva/TpEvaJob.java new file mode 100644 index 0000000..75da0ab --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/thread/eva/TpEvaJob.java @@ -0,0 +1,369 @@ +package com.imitate.web.module.game.thread.eva; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.imitate.common.constant.TpCsts; +import com.imitate.common.enums.ErrorCodeEnum; +import com.imitate.common.exception.BusinessException; +import com.imitate.common.k8s.bean.CreatePodResult; +import com.imitate.common.k8s.mgr.ClusterManager; +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.k8s.service.RunPodService; +import com.imitate.common.k8s.util.ContainerUtil; +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.common.util.*; +import com.imitate.common.constant.BuildResultCsts; +import com.imitate.web.module.game.pojo.SimpleBuildResult; +import com.imitate.web.module.game.service.*; +import io.fabric8.kubernetes.api.model.Pod; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * 实训评测 + */ +@Service +@Scope("prototype") +public class TpEvaJob extends EvaJob { + private static final Logger logger = LoggerFactory.getLogger(TpEvaJob.class); + + public static final String HOST = "tcp://121.40.129.71:48835"; + + private static final String clientID = "clientFirst"; + private String TOPIC; + private static MqttClient client; + private MqttConnectOptions options; + private String user = ""; + private String password = ""; + + + @Autowired + private GameService gameService; + @Autowired + private KubernetesService kubernetesService; + @Autowired + private AppConfig appConfig; + @Autowired + private RunPodService runPodService; + @Autowired + private ClusterManager clusterManager; + @Autowired + private GitService gitService; + + @Qualifier("gitTaskExecutor") + @Autowired + private ThreadPoolTaskExecutor gitTaskExecutor; + @Autowired + private TestCaseService testCaseService; + @Autowired + private DiskService diskService; + + + @Override + public void run() { + // 参数处理 + String buildID = buildParams.getString("buildID"); + String tpiID = buildParams.getString("tpiID"); + int timeLimit = buildParams.getInteger("timeLimit"); + String evaluateStartTime = buildParams.getString("evaluateStartTime"); + final String cluster = buildParams.getString("cluster"); + Boolean localNode = buildParams.get("localNode") == null ? Boolean.FALSE : buildParams.getBoolean("localNode"); + String tpiWorkspace = diskService.getTpiWorkspaceHostPath(tpiID); + Integer podType = buildParams.getInteger("podType"); + String type = TpUtils.cvtEvaPodType(podType); + String secretRepospace = buildParams.getString("secretRepospace"); + String bigDataFile = buildParams.getString("bigDataFile"); + String testCases = buildParams.getString("testCases"); + JSONArray cases = JSONArray.parseArray(testCases); + boolean isLocalCluster = clusterManager.isLocalCluster(cluster); + int executeTimeLimit = buildParams.getInteger("executeTime") == null ? timeLimit + : buildParams.getInteger("executeTime"); + + // 启动中回传 + String simpleCallBackUrl = buildParams.getString("simpleCallBackUrl"); + gameService.encapsulateSimpleBuildResult(simpleCallBackUrl, BuildResultCsts.TEXT_MSG_SERVICE_START, SimpleBuildResult.STATUS_MSG, + null, buildID); + + + //若是树莓派评测发送消息 + String raspberryScript = tpiWorkspace + File.separator + TpCsts.TP_UNIFY_REPO_NAME + File.separator + TpCsts.TP_RASPBERRY_REPO_SCRIPT; + if (FileUtils.getFile(raspberryScript).exists()) { + JSONObject param = new JSONObject(); + param.put("tpiID", buildParams.getString("tpiID")); + param.put("tmpScript", buildParams.getString("tpmScript")); + param.put("tpiGitURL", buildParams.getString("tpiGitURL")); + param.put("buildID", buildParams.getString("buildID")); + param.put("testCases", buildParams.getString("testCases")); + param.put("tpmIdentifier", buildParams.getString("tpmIdentifier")); + //开启线程 订阅与发布主题 + new Thread(() -> { + try { + client = new MqttClient(HOST, clientID, new MemoryPersistence()); + options = new MqttConnectOptions(); + options.setCleanSession(true); + options.setKeepAliveInterval(10); + options.setConnectionTimeout(50); + client.setCallback(new MqttCallBackService()); + sub("result", param.toJSONString()); + } catch (MqttException e) { + e.printStackTrace(); + } + }).start(); + + //30秒同步等待结果 + int i = 30; + while (i > 0) { + try { + Thread.sleep(1000); + i--; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + + + // 生成评测脚本 + generateEvaluateScript(tpiID, isLocalCluster); + + // git pull + Map mdcContext = MDC.getCopyOfContextMap(); + Future pull = gitTaskExecutor.submit(() -> evaluateStepPull(tpiID, cluster, tpiWorkspace, secretRepospace, isLocalCluster, mdcContext)); + + + // 获取pod + Pod pod = evaluateStepGetPod(tpiID, timeLimit, evaluateStartTime, cluster, localNode, type, secretRepospace, bigDataFile, cases, pull); + if (pod == null) { + return; + } + + // 下载代码失败 + String pullStatus = getPullStatus(tpiID, pull, evaluateStartTime, timeLimit); + if (BuildResultCsts.DOWNLOAD_STATUS_FAIL.equals(pullStatus)) { + String msg = GameHelper.getResult(BuildResultCsts.CLONE_CODE_FAIL, cases.size(), BuildResultCsts.DOWNLOAD_STATUS_FAIL, + BuildResultCsts.CREATE_POD_STATUS_SUCCESS); + gameService.encapsulateBuildResult(cluster, msg, buildParams, cases); + return; + } + + // 服务启动完成回传 + gameService.encapsulateSimpleBuildResult(simpleCallBackUrl, BuildResultCsts.TEXT_MSG_SERVICE_SUCCESS, SimpleBuildResult.STATUS_MSG, + null, buildID); + + // 在pod中执行评测脚本 + kubernetesService.executeShellInPod(cluster, buildParams, pod, executeTimeLimit, buildParams.getInteger("isPublished"), buildParams.getInteger("instanceChallenge"), + cases); + } + + + /** + * 发布与订阅 + * + * @param topic + * @param jsonStr + * @throws MqttException + */ + private void sub(String topic, String jsonStr) throws MqttException { + //订阅主题 + TOPIC = topic; + MqttTopic tp = client.getTopic(TOPIC); + //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息 + options.setWill(tp, "close".getBytes(), 1, true); + client.connect(options); + int[] Qos = {1}; + String[] topic1 = {TOPIC}; + client.subscribe(topic1, Qos); + //发布主题 + MqttMessage mqttMessage = new MqttMessage(); + mqttMessage.setId(1); + mqttMessage.setQos(1); + mqttMessage.setPayload(jsonStr.getBytes(StandardCharsets.UTF_8)); + client.publish(buildParams.getString("tpiID"), mqttMessage); + } + + + /** + * 获取pod步骤 + * + * @param tpiID tpiId + * @param timeLimit 执行时限 + * @param evaluateStartTime + * @param cluster + * @param localNode + * @param type + * @param secretRepospace + * @param bigDataFile + * @param cases + * @param cases + * @param pull + * @return + */ + private Pod evaluateStepGetPod(String tpiID, int timeLimit, String evaluateStartTime, String cluster, Boolean localNode, String type, String secretRepospace, String bigDataFile, JSONArray cases, Future pull) { + Instant podStartInstant = Instant.now(); + + // 创建pod + int createPodTimeLimit = Math.max(ContainerUtil.getContainersStartTime(buildParams.getString("containers")), 15); + buildParams.put("mountTestCaseDir", testCaseService.containsFileCase(cases)); + CreatePodResult result = runPodService.createRunPod(cluster, buildParams, tpiID, type, createPodTimeLimit, timeLimit, localNode, bigDataFile); + Pod pod = result.getPod(); + setBuildParamsPodInfo(buildParams, result); + if (pod != null) { + // 记录创建pod耗时 + String createPodCost = TimeCostUtils.getStepTimeCost(podStartInstant); + buildParams.put("createPodCost", createPodCost); + runPodService.delayRunPodExpireTime(pod.getMetadata().getName(), timeLimit + appConfig.getEvaluatePodDefaultDelayDeleteTime()); + } else { + logger.error("实训 {} 创建pod 失败", tpiID); + String pullStatus = getPullStatus(tpiID, pull, evaluateStartTime, timeLimit); + String msgResultTip = BuildResultCsts.RES_SCALE_TIP; + // 没有images造成创建pod失败报系统繁忙 + if (CreatePodResult.CREATE_POD_STATUS_FAIL_PULL_IMAGE == result.getStatus()) { + msgResultTip = BuildResultCsts.PULL_IMAGE_FAIL; + } + if (CreatePodResult.CREATE_POD_STATUS_FAIL_DELETE_NOW == result.getStatus()) { + msgResultTip = BuildResultCsts.EVA_RETRY_ERROR_TIP; + } + String msg = GameHelper.getResult(msgResultTip, cases.size(), pullStatus, BuildResultCsts.CREATE_POD_STATUS_FAIL); + gameService.encapsulateBuildResult(cluster, msg, buildParams, cases); + if (StringUtils.isNotEmpty(secretRepospace)) { + if (clusterManager.isLocalCluster(cluster)) { + gameService.cleanSecretFileContent(secretRepospace); + } else { + gitService.remoteClusterDeleteSecret(cluster, Boolean.TRUE, secretRepospace); + } + } + return null; + } + return pod; + } + + private void generateEvaluateScript(String tpiID, boolean isLocalCluster) { + if (isLocalCluster) { + // 每次评测均生成新TPI评测脚本 + String tpmScript = Base64Util.decode(buildParams.getString("tpmScript")); + try { + String tpiProtectSpace = diskService.buildTpiProtectSpace(diskService.getWorkspaceHostPath(tpiID), tpiID); + gameService.generateTpiEvaluateShellScript(tpmScript, tpiProtectSpace, TpCsts.TP_UNIFY_REPO_NAME); + } catch (BusinessException e) { + logger.error(ErrorCodeEnum.EVALUATION_SHELL_FAIL.getValue(), "系统繁忙:生成" + tpiID + "评测脚本失败", e); + } + } + } + + /** + * 拉取代码 + * + * @param tpiID tpiID + * @param cluster 集群 + * @param tpiWorkspace tpi工作目录 + * @param secretRepospace 私密版本库目录 + * @param isLocalCluster 是否本地集群 + * @param logbackContext logback上下文 + * @return 拉取是否成功 + */ + private Boolean evaluateStepPull(String tpiID, String cluster, String tpiWorkspace, String secretRepospace, boolean isLocalCluster, Map logbackContext) { + logger.info("pull started, tpiID: {}", tpiID); + try { + MDC.setContextMap(logbackContext); + Instant pullStartInstant = Instant.now(); + + if (isLocalCluster) { + gameService.gitPull(tpiID, tpiWorkspace, buildParams.getString("tpiGitURL"), TpCsts.TP_UNIFY_REPO_NAME, + buildParams.getInteger("contentModified")); + + + // 克隆私密版本库 + if (StringUtils.isNotEmpty(secretRepospace)) { + String secretGitUrl = buildParams.getString("secretGitUrl"); + String secretDir = buildParams.getString("secretDir"); + String secretTpiWorkspace = + diskService.getTpiSecretWorkspace(tpiID); + gameService.gitPull(tpiID, secretTpiWorkspace, secretGitUrl, secretDir, 0); + gameService.copySecretFileContent(secretRepospace, tpiID); + } + } else { + // 由于混合云集群暂时只用本地配置,所以默认 isLocal 为 true + Boolean pullResult = gitService.remoteClusterPull(cluster, Boolean.TRUE, tpiID, buildParams); + if (!pullResult) { + logger.error("系统繁忙:调用远程服务接口下载代码失败,tpiID: {}", tpiID); + return false; + } + } + buildParams.put("pullCost", TimeCostUtils.getStepTimeCost(pullStartInstant)); + return true; + } catch (Exception e) { + logger.error("系统繁忙:下载实训" + tpiID + "代码失败", e); + return false; + } + } + + private String getPullStatus(String tpiID, Future pull, String evaluateStartTime, int timeLimit) { + boolean pullResult = false; + try { + int timeout = TimeCostUtils.getRemainingTime(evaluateStartTime, timeLimit); + pullResult = pull.get(timeout, TimeUnit.SECONDS); + } catch (InterruptedException e) {// 不应该发生 + logger.error("等待下载实训{}代码被中断", tpiID, e); + } catch (ExecutionException e) {// 不应该发生 + logger.error("等待下载实训{}代码出错", tpiID, e); + } catch (TimeoutException e) { + logger.error("等待下载实训{}代码超时", tpiID, e); + } + if (pullResult) { + return BuildResultCsts.DOWNLOAD_STATUS_SUCCESS; + } + return BuildResultCsts.DOWNLOAD_STATUS_FAIL; + } + + /** + * 设置Pod的元信息到buildParams + */ + public void setBuildParamsPodInfo(JSONObject buildParams, CreatePodResult result) { + Pod pod = result.getPod(); + if (pod != null) { + buildParams.put("podName", pod.getMetadata().getName()); + buildParams.put("nodeName", pod.getSpec().getNodeName()); + buildParams.put("nodeIp", pod.getStatus().getHostIP()); + String createTime = pod.getMetadata().getCreationTimestamp(); + if (createTime.endsWith("Z")) { + createTime = createTime.substring(0, createTime.length() - 1); + } + LocalDateTime time = LocalDateTime.parse(createTime); + // 转化为东八区时间 + if (time.isBefore(LocalDateTime.now().plusHours(-1))) { + time = time.plusHours(8); + } + buildParams.put("k8sCreateTime", time); + } else { + buildParams.put("nodeName", result.getNodeName()); + } + } + + @Override + public String toString() { + return "BuildThread{" + buildParams.toJSONString() + '}'; + } + + +} diff --git a/web/src/main/java/com/imitate/web/module/game/util/AviatorUtils.java b/web/src/main/java/com/imitate/web/module/game/util/AviatorUtils.java new file mode 100644 index 0000000..79afa38 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/util/AviatorUtils.java @@ -0,0 +1,32 @@ +package com.imitate.web.module.game.util; + +import com.googlecode.aviator.AviatorEvaluator; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author zmr + */ +public class AviatorUtils { + + static { + AviatorEvaluator.getInstance().useLRUExpressionCache(1000); + AviatorEvaluator.addFunction(new ListCmpFunction()); + } + + public static Boolean aviatorEvaluator(String expression, Object expect, Object actual) { + Map env = new HashMap(); + if (expression.startsWith("string.")) { + env.put("expect", expect); + env.put("actual", actual); + } else if (expression.startsWith("list.")) { + env.put("expectElem", expect); + env.put("actualElem", actual); + } else { + env.put("expect", Double.parseDouble(expect.toString())); + env.put("actual", Double.parseDouble(actual.toString())); + } + return (boolean) AviatorEvaluator.execute(expression, env, Boolean.TRUE); + } +} diff --git a/web/src/main/java/com/imitate/web/module/game/util/ListCmpFunction.java b/web/src/main/java/com/imitate/web/module/game/util/ListCmpFunction.java new file mode 100644 index 0000000..daa360f --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/game/util/ListCmpFunction.java @@ -0,0 +1,99 @@ +package com.imitate.web.module.game.util; + +import cn.hutool.core.util.NumberUtil; +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.Expression; +import com.googlecode.aviator.runtime.function.AbstractFunction; +import com.googlecode.aviator.runtime.type.AviatorBoolean; +import com.googlecode.aviator.runtime.type.AviatorJavaType; +import com.googlecode.aviator.runtime.type.AviatorObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ListCmpFunction extends AbstractFunction { + + private static final long serialVersionUID = 1L; + + private static final String CONSTAINS = "string.contains"; + + @Override + public AviatorObject call(final Map env, final AviatorObject arg1) { + + final AviatorObject arg2 = new AviatorJavaType("separator"); + env.put("separator", " "); + + final AviatorObject arg3 = new AviatorJavaType("expectElem"); + final AviatorObject arg4 = new AviatorJavaType("actualElem"); + + return call(env, arg1, arg2, arg3, arg4); + } + + @Override + public AviatorObject call(final Map env, final AviatorObject arg1, final AviatorObject arg2) { + + final AviatorObject arg3 = new AviatorJavaType("expectElem"); + final AviatorObject arg4 = new AviatorJavaType("actualElem"); + + return call(env, arg1, arg2, arg3, arg4); + } + + @Override + public AviatorObject call(final Map env, final AviatorObject arg1, final AviatorObject arg2, + final AviatorObject arg3, final AviatorObject arg4) { + + String exp = (String) arg1.getValue(env); + + String separator = (String) arg2.getValue(env); + String expect = (String) arg3.getValue(env); + String actual = (String) arg4.getValue(env); + + Expression expression = AviatorEvaluator.compile(exp, true); + List eList = cvtToList(expect, separator); + List aList = cvtToList(actual, separator); + if (eList.size() != aList.size()) { + return AviatorBoolean.FALSE; + } + + Map p = new HashMap<>(); + + for (int i = 0; i < eList.size(); i++) { + Object e = eList.get(i); + Object a = aList.get(i); + p.put("expectElem", e); + p.put("actualElem", a); + + // TODO 对结果类型进行判断 + Boolean r = (Boolean) expression.execute(p); + if(!r) { + return AviatorBoolean.FALSE; + } + } + + return AviatorBoolean.TRUE; + } + + private List cvtToList(String str, String separator) { + List list = new ArrayList<>(); + String[] arr = str.split(separator); + for (String s : arr) { + if (NumberUtil.isNumber(s)) { + list.add(Double.parseDouble(s)); + }else { + list.add(s); + } + } + + return list; + + } + + @Override + public String getName() { + return "list.forEach"; + } + +} + diff --git a/web/src/main/java/com/imitate/web/module/task/DelHisCreateImageTask.java b/web/src/main/java/com/imitate/web/module/task/DelHisCreateImageTask.java new file mode 100644 index 0000000..71746f3 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/task/DelHisCreateImageTask.java @@ -0,0 +1,49 @@ +package com.imitate.web.module.task; + +import com.imitate.common.constant.TpCsts; +import com.imitate.common.util.JedisUtil; +import com.imitate.common.util.TpUtils; +import com.imitate.web.module.game.service.ImageService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +/** + * 清理历史创建的镜像 + */ +@Component +public class DelHisCreateImageTask { + private final static Logger logger = LoggerFactory.getLogger(DelHisCreateImageTask.class); + + @Autowired + private ImageService imageService; + + /** + * 创建镜像保存时间(30天) + */ + private static final long HIS_CREATE_IMAGE_SAVE_TIME_MILLIS = 30 * 86400; + + @Scheduled(cron = "${DelHisCreateImageTask}") + public void work() { + logger.info("[start]定时任务: 清理历史创建的镜像"); + Map imageInfo = JedisUtil.hgetAll(TpCsts.CREATE_IMAGE_MAP); + if (imageInfo != null) { + Set imageNames = imageInfo.keySet(); + for (String imageName : imageNames) { + if (!TpUtils.isCreateImageName(imageName)) { + continue; + } + Long createTime = Long.valueOf(imageInfo.get(imageName)); + if ((System.currentTimeMillis() - createTime) / 1000 > HIS_CREATE_IMAGE_SAVE_TIME_MILLIS) { + imageService.deleteRepoImage(imageName); + } + } + } + logger.info("[end]定时任务: 清理历史创建的镜像"); + } +} \ No newline at end of file diff --git a/web/src/main/java/com/imitate/web/module/task/GitInitTask.java b/web/src/main/java/com/imitate/web/module/task/GitInitTask.java new file mode 100644 index 0000000..c1365d8 --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/task/GitInitTask.java @@ -0,0 +1,29 @@ +package com.imitate.web.module.task; + +import com.imitate.common.sys.settings.AppConfig; +import com.imitate.web.module.game.service.GitAccountService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * git初始化凭证设置 + */ +@Component +public class GitInitTask { + + @Autowired + private AppConfig appConfig; + + @Autowired + private GitAccountService gitAccountService; + + @PostConstruct + public void init() { + String gitHost = appConfig.getGitIP(); + gitAccountService.gitCredentialStore(gitHost, "http"); + String gitDomainName = "git.educoder.net"; + gitAccountService.gitCredentialStore(gitDomainName, "https"); + } +} diff --git a/web/src/main/java/com/imitate/web/module/task/ScriptInitTask.java b/web/src/main/java/com/imitate/web/module/task/ScriptInitTask.java new file mode 100644 index 0000000..ee611fd --- /dev/null +++ b/web/src/main/java/com/imitate/web/module/task/ScriptInitTask.java @@ -0,0 +1,53 @@ +package com.imitate.web.module.task; + +import com.imitate.common.k8s.service.DiskService; +import com.imitate.common.sys.service.ResourceFileService; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; + +/** + * execeva.sh初始化 + */ +@Component +public class ScriptInitTask { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private ResourceFileService resourceFileService; + + @Autowired + private DiskService diskService; + + @PostConstruct + public void init() { + writeFile("execEva.sh"); + writeFile("execStepOut.sh"); + writeFile("execEva.py"); + writeFile("evaluate.sh"); + writeFile("kill.sh"); + } + + private void writeFile(String scriptName) { + String[] workspaceNFSConfigs = diskService.getWorkspaceNFSConfigs(); + for (String platformPath : workspaceNFSConfigs) { + String path = diskService.buildTpiPlatformHostPath(platformPath); + path = path + File.separator + scriptName; + try { + String content = resourceFileService.getResourceFileContent("evaluate/" + scriptName); + FileUtils.writeStringToFile(new File(path), content, "UTF-8"); + } catch (IOException e) { + logger.error("{} 初始化失败", scriptName, e); + throw new RuntimeException(scriptName + "初始化失败"); + } + } + + } +} diff --git a/web/src/main/java/com/imitate/web/params/DemoParam.java b/web/src/main/java/com/imitate/web/params/DemoParam.java new file mode 100644 index 0000000..47b0570 --- /dev/null +++ b/web/src/main/java/com/imitate/web/params/DemoParam.java @@ -0,0 +1,20 @@ +package com.imitate.web.params; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class DemoParam { + + @NotBlank(message = "课程分类名称不可为空") + private String courseType; + + + @NotNull(message = "ID不能为空") + private Long id; + +} diff --git a/web/src/main/java/com/imitate/web/params/GameEvaluateParam.java b/web/src/main/java/com/imitate/web/params/GameEvaluateParam.java new file mode 100644 index 0000000..cddd7a5 --- /dev/null +++ b/web/src/main/java/com/imitate/web/params/GameEvaluateParam.java @@ -0,0 +1,139 @@ +package com.imitate.web.params; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @Author: youys + * @Date: 2022/6/30 + * @Description: + */ +@Data +public class GameEvaluateParam { + + @NotBlank(message = "实训实例的ID不能为空") + private String tpiID; + /** + * 实训ID + */ + private String tpID; + /** + * 评测等级 + */ + private Long priority; + + @NotBlank(message = "实训实例的版本库地址不能为空") + private String tpiGitURL; + + @NotBlank(message = "关卡ID不能为空") + private String buildID; + + @NotNull(message = "实训是否已经发布不能为空") + private Integer isPublished; + + @NotBlank(message = "当前处在第几关不能为空") + private String instanceChallenge; + + @NotBlank(message = "测试集不能为空") + private String testCases; + /** + * 空格处理:0不忽略空格,1忽略输出结果的首尾空格、换行符,2忽略所有空格 + */ + @NotNull(message = "trimBlank不能为空") + private Integer trimBlank; + /** + * 测试用例结果判断类型,1表达式 + */ + private Integer testCasesType; + /** + * 测试用例结果判断表达式,base64编码 + */ + private String testCasesExp; + /** + * tpm评测脚本,base64编码 + */ + @NotBlank(message = "tpmScript不能为空") + private String tpmScript; + /** + * 评测执行时间限制 + */ + private Integer timeLimit; + /** + * 执行时间限制 + */ + private Integer executeTime; + /** + * 是否是重复评测 + */ + @NotBlank(message = "") + private String resubmit; + /** + * 是否是时间轮询请求 + */ + @NotBlank(message = "") + Integer times; + /** + * 容器中需要被映射的端口 + */ + Integer needPortMapping; + /** + * pod类型(0.evaluate,1.webssh,2.evassh, 3.vnc) + */ + @NotBlank(message = "podType不能为空") + Integer podType; + /** + * 需要传文件的实训,给出文件存放路径(一个目录)及文件类型 + */ + private String file; + /** + * 容器,base64编码 + */ + @NotBlank(message = "containers不能为空") + private String containers; + + /** + * 文件是否修改的标志 + */ + @NotNull(message = "contentModified不能为空") + private Integer contentModified; + /** + * 每一次评测的唯一标识 + */ + private String secKey; + /** + * 私密版本库地址 + */ + private String secretGitUrl; + /** + * 私密版本库路径 + */ + private String secretDir; + /** + * 评测结果回调地址 + */ + private String callBackUrl; + /** + * 简单消息回调地址 + */ + private String simpleCallBackUrl; + /** + * 额外参数,无需解析,原样返回 + */ + private String extras; + /** + * 实训的identifier + */ + private String tpmIdentifier; + /** + * 大数据集文件目录 + */ + private String bigDataFile; + /** + * 仅运行 + */ + private Integer runOnly; + + +} diff --git a/web/src/main/java/com/imitate/web/params/GetVsCodeParam.java b/web/src/main/java/com/imitate/web/params/GetVsCodeParam.java new file mode 100644 index 0000000..d66d562 --- /dev/null +++ b/web/src/main/java/com/imitate/web/params/GetVsCodeParam.java @@ -0,0 +1,27 @@ +package com.imitate.web.params; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @Author: youys + * @Date: 2022/6/30 + * @Description: + */ + +@Data +public class GetVsCodeParam { + @NotBlank(message = "tpiID不能为空") + private String tpiID; + @NotNull(message = "podType不能为空") + private Integer podType = 5; + private String tpiGitURL; + @NotBlank(message = "containers不能为空") + private String containers; + private Integer needPortMapping; + private String bigDataFile; + private Boolean createImage; + private Integer survivalSecond; +} diff --git a/web/src/main/java/com/imitate/web/params/VsCodeDeleteParam.java b/web/src/main/java/com/imitate/web/params/VsCodeDeleteParam.java new file mode 100644 index 0000000..ed0169e --- /dev/null +++ b/web/src/main/java/com/imitate/web/params/VsCodeDeleteParam.java @@ -0,0 +1,24 @@ +package com.imitate.web.params; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Author: youys + * @Date: 2022/7/1 + * @Description: + */ +@Data +public class VsCodeDeleteParam { + + @NotBlank(message = "tpiID不能为空") + private String tpiID; + + @NotBlank(message = "identifier不能为空") + private String digestKey; + + @NotBlank(message = "identifier不能为空") + private String identifier; + +} diff --git a/web/src/main/java/com/imitate/web/persistence/beans/SysLog.java b/web/src/main/java/com/imitate/web/persistence/beans/SysLog.java new file mode 100644 index 0000000..8ce16ad --- /dev/null +++ b/web/src/main/java/com/imitate/web/persistence/beans/SysLog.java @@ -0,0 +1,216 @@ + +package com.imitate.web.persistence.beans; + +import java.io.Serializable; +import java.time.LocalDateTime; + + +/** + * 系统日志 + * @author 悟空 + */ +public class SysLog implements Serializable { + + private Long id; + + /** + * 用户操作 + */ + private String operation; + + /** + * 模块名称 + */ + private String moduleName; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求参数 + */ + private String params; + + /** + * 执行时长(毫秒) + */ + private Integer time; + + /** + * IP地址 + */ + private String ip; + + /** + * 方法路由 + */ + private String methodPath; + + + /** + * 类路由 + */ + private String classPath; + + + /** + * 请求类型 GET或POST + */ + private String httpType; + + + + /** + * 返回结果 + */ + private String result; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + private LocalDateTime startTime; + private LocalDateTime endTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getParams() { + return params; + } + + public void setParams(String params) { + this.params = params; + } + + public Integer getTime() { + return time; + } + + public void setTime(Integer time) { + this.time = time; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public String getMethodPath() { + return methodPath; + } + + public void setMethodPath(String methodPath) { + this.methodPath = methodPath; + } + + public String getClassPath() { + return classPath; + } + + public void setClassPath(String classPath) { + this.classPath = classPath; + } + + public String getHttpType() { + return httpType; + } + + public void setHttpType(String httpType) { + this.httpType = httpType; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + @Override + public String toString() { + return "SysLog{" + + "id=" + id + + ", operation='" + operation + '\'' + + ", moduleName='" + moduleName + '\'' + + ", method='" + method + '\'' + + ", params='" + params + '\'' + + ", time=" + time + + ", ip='" + ip + '\'' + + ", methodPath='" + methodPath + '\'' + + ", classPath='" + classPath + '\'' + + ", httpType='" + httpType + '\'' + + ", result='" + result + '\'' + + ", createTime=" + createTime + + ", updateTime=" + updateTime + + '}'; + } +} diff --git a/web/src/main/java/com/imitate/web/persistence/beans/Windows.java b/web/src/main/java/com/imitate/web/persistence/beans/Windows.java new file mode 100644 index 0000000..4a4b717 --- /dev/null +++ b/web/src/main/java/com/imitate/web/persistence/beans/Windows.java @@ -0,0 +1,54 @@ +package com.imitate.web.persistence.beans; + +import com.imitate.common.util.AbstractDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.persistence.Column; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * windows实例信息 + * @author yanchao + */ +@Data +@Table(name = "windows_info") +@EqualsAndHashCode(callSuper = false) +public class Windows extends AbstractDO { + + @Column(name = "uniq_id") + private String uniqId; + + @Column(name = "instance_id") + private String instanceId; + + @Column(name = "user_id") + private String userID; + + @Column(name = "template_name") + private String templateName; + + @Column(name = "port") + private String port; + + @Column(name = "vnc_port") + private String vncPort; + + @Column(name = "forward_table_id") + private String forwardTableId; + + @Column(name = "forward_entry_id") + private String forwardEntryId; + + @Column(name = "vnc_forward_entry_id") + private String vncForwardEntryId; + + @Column(name = "auto_release_time") + private LocalDateTime autoReleaseTime; + + @Column(name = "status") + private Integer status; +} + + diff --git a/web/src/main/java/com/imitate/web/persistence/mapper/WindowsMapper.java b/web/src/main/java/com/imitate/web/persistence/mapper/WindowsMapper.java new file mode 100644 index 0000000..ff4982d --- /dev/null +++ b/web/src/main/java/com/imitate/web/persistence/mapper/WindowsMapper.java @@ -0,0 +1,11 @@ +package com.imitate.web.persistence.mapper; + + + +import com.imitate.web.persistence.beans.Windows; + + + +public interface WindowsMapper { + Windows selectByUniqId(String uniqId); +} \ No newline at end of file diff --git a/web/src/main/java/com/imitate/web/vo/GameEvaluateVO.java b/web/src/main/java/com/imitate/web/vo/GameEvaluateVO.java new file mode 100644 index 0000000..b26fd7b --- /dev/null +++ b/web/src/main/java/com/imitate/web/vo/GameEvaluateVO.java @@ -0,0 +1,17 @@ +package com.imitate.web.vo; + +import lombok.Data; + +/** + * @Author: youys + * @Date: 2022/6/30 + * @Description: + */ +@Data +public class GameEvaluateVO { + + private Integer ableToCreate; + private Long costTime; + private Integer waitNum; + private Integer port; +} diff --git a/web/src/main/java/com/imitate/web/vo/WindowsInfoDelegate.java b/web/src/main/java/com/imitate/web/vo/WindowsInfoDelegate.java new file mode 100644 index 0000000..1dada5b --- /dev/null +++ b/web/src/main/java/com/imitate/web/vo/WindowsInfoDelegate.java @@ -0,0 +1,50 @@ +package com.imitate.web.vo; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.imitate.common.util.BasePageCondition; +import com.imitate.web.persistence.beans.Windows; + + +/** + * @author yanchao + */ +public class WindowsInfoDelegate extends BasePageCondition { + private final Windows windowsInfo; + + { + windowsInfo = new Windows(); + } + + + @JsonIgnore + public Windows getWindowsInfo() { + return windowsInfo; + } + + + + + + public void setId(Long id) { + getWindowsInfo().setId(id); + } + + public Long getId() { + return getWindowsInfo().getId(); + } + + public String getUniqId() { + return getWindowsInfo().getUniqId(); + } + + public String getVncForwardEntryId() { + return getWindowsInfo().getVncForwardEntryId(); + } + + public void setStatus(Integer status) { + getWindowsInfo().setStatus(status); + } + + +} diff --git a/web/src/main/resources/application.properties b/web/src/main/resources/application.properties new file mode 100644 index 0000000..24d1967 --- /dev/null +++ b/web/src/main/resources/application.properties @@ -0,0 +1,56 @@ +server.port=8088 +server.servlet.context-path=/openi +server.max-http-header-size=8192 +server.compression.enabled=true +server.compression.min-response-size=1024 +server.compression.mime-types=text/plain,text/css,text/xml,text/javascript,application/json,application/javascript,application/xml,application/xml+rss,application/x-javascript,application/x-httpd-php,image/jpeg,image/gif,image/png +server.tomcat.uri-encoding=UTF-8 + + + +spring.config.import=classpath:common.properties + + + +# mysql +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://rm-bp13v5020p7828r5rso.mysql.rds.aliyuncs.com:3306/imitate_sys?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false +spring.datasource.username=testeducoder +spring.datasource.password=TEST@123 +spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +spring.datasource.initialSize=200 +spring.datasource.maxActive=400 +spring.datasource.minIdle=200 +spring.datasource.validationQuery=select 1 +spring.datasource.testOnBorrow=false +spring.datasource.testOnReturn=false +spring.datasource.testWhileIdle=true +mybatis.type-aliases-package=com.imitate.web.persistence.beans +mybatis.mapperLocations=classpath*:mybatis/*.xml +mybatis.configuration.map-underscore-to-camel-case=true + +#mapper +mapper.mappers=com.imitate.common.util.BaseMapper +mapper.not-empty=false +mapper.identity=MYSQL + + +# pagehelper +pagehelper.helper-dialect=mysql +pagehelper.reasonable=true +pagehelper.support-methods-arguments=true +pagehelper.params=count=countSql + + +#showSql +logging.level.com.imitate.web.persistence.mapper=debug + + +# redis +spring.redis.host=127.0.0.1 +spring.redis.port=6379 +spring.redis.password= + + + + diff --git a/web/src/main/resources/evaluate/evaluate.sh b/web/src/main/resources/evaluate/evaluate.sh new file mode 100644 index 0000000..752a17b --- /dev/null +++ b/web/src/main/resources/evaluate/evaluate.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# 待执行的评测文件 +sourceClassNames="$3" + +# 执行命令 +executeCommand="python3" + +# 获取测试用例的输入(请勿改动此行语句) +input=$2; OLD_IFS="$IFS"; IFS=,; ins=($input); IFS="$OLD_IFS" + +compileResult=$(echo -n "compile successfully" | base64) + +# 执行函数 +execute(){ + # 当前关卡的执行目标文件 + sourceClassName=${sourceClassNames[$1 - 1]} + # 当前关卡号 + challengeStage=$1 + # 循环获取各测试用例下的实际输出 + res_usage="{\"testSetUsages\":[" + output='' + i=0 + while [[ i -lt ${#ins[*]} ]]; do + #echo 0 > /sys/fs/cgroup/memory/memory.max_usage_in_bytes + startCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + result=$(echo "${ins[$i]}" | base64 -d | $executeCommand $sourceClassName 2>&1 | tee /data/workspace/user.out | base64) + #拼接输出结果 + endCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + let testSetCpuUsage=$endCpuUsage-$startCpuUsage + maxMemUsage=$(cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes) + res_usage="$res_usage{\"testSetTime\":\"$testSetCpuUsage\",\"testSetMem\":\"$maxMemUsage\"}," + output=$output\"$result\", + let i++ + done + output="[${output%?}]" +} +execute $1 +res_usage="${res_usage::-1}" +res_usage="$res_usage]}" +res_usage=$(echo -ne "$res_usage"|base64) + +# 返回评测结果 +returnResult(){ + result="{\"compileResult\":\"$compileResult\",\"out\":$output}" + echo $result +} +returnResult \ No newline at end of file diff --git a/web/src/main/resources/evaluate/execEva.py b/web/src/main/resources/evaluate/execEva.py new file mode 100644 index 0000000..961bb18 --- /dev/null +++ b/web/src/main/resources/evaluate/execEva.py @@ -0,0 +1,105 @@ +# jupyter评测执行脚本 + +#!/usr/bin/python3 +# -*- coding:utf-8 -*- + +from pickle import TRUE +from re import I +import re +from tkinter.messagebox import RETRY +from click import command +from pathlib import Path +import sys +import base64 +import os +import json +import subprocess + + +class Evalute: + + def __init__(self,input,jupyter_file,timeout): + try: + f = open(jupyter_file) + f.close() + except FileNotFoundError: + print("Error: 没有找到文件 %s,%s",jupyter_file) + else: + #存在 + self.encode_input = input + self.jupyter_file = jupyter_file + self.decode_input = self.decode_case(input) + self.timeout = timeout + + + + #解析测试集 + def decode_case(self,input): + input_list = [] + if input == "IA==": + input_list.append(input) + else: + #判断整个base64是否包含逗号,若包含则切割,不包含则解析返回 + if input.find(",") != -1: + input_list = [] + input_list = input.split(",") + else: + input_list.append(input) + return input_list + + + #检查测试集是否为空 + def check_input_is_null(self): + #假设无测试集的情况是空格 + if self.decode_input[0] == "IA==": + return True + return False + + + #初始化,生成执行命令列表 + def init(self): + self.cmd_list = [] + if self.check_input_is_null() == False: + for param in self.decode_input: + str = "echo \"" + param + "\" | base64 -d |" + "/usr/bin/python3.6 " + self.jupyter_file + " 2>&1 | base64" + self.cmd_list.append(str) + if self.check_input_is_null() == True: + str = "/usr/bin/python3 " + self.jupyter_file + " 2>&1 |base64" + self.cmd_list.append(str) + return + + #执行评测并保存结果返回列表 + def run(self): + #评测结果 + self.eva_result = "" + r = [] + eva_result_direct = {} + eva_result_direct["compileResult"] = "Y29tcGlsZSBzdWNjZXNzZnVsbHk" + for num in self.cmd_list: + ret = os.popen(num,'r').read() + r.append(ret) + eva_result_direct["out"] = r + self.eva_result = json.dumps(eva_result_direct, ensure_ascii=False) + + + # 脚本只做结果输出 + def print_console(self): + print(self.eva_result) + return + + + #开始评测 + def start(self): + #评测初始化 + self.init() + #执行存结果 + self.run() + #输出结果到console + self.print_console() + + + +if __name__ == '__main__': + # 参数:测试集每组以逗号分隔base64编码;jupyter源代码路径;timeout; + a = Evalute(sys.argv[1],sys.argv[2],sys.argv[3]) + a.start() \ No newline at end of file diff --git a/web/src/main/resources/evaluate/execEva.sh b/web/src/main/resources/evaluate/execEva.sh new file mode 100644 index 0000000..2a0876b --- /dev/null +++ b/web/src/main/resources/evaluate/execEva.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +startCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) +timeout $1 bash /data/platform/eva/execStepOut.sh $2 $3 $4 $5 $6 +exitStatus=$? +if [ $exitStatus -eq 124 ] || [ $exitStatus -eq 137 ];then + endCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + let evaCpuUsage=$endCpuUsage-$startCpuUsage + evaMaxMemUsage=$(cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes) + nodeLoadAvg=$(cat /proc/loadavg) + echo "{\"exitStatus\":$exitStatus,\"evaCpuUsage\":$evaCpuUsage,\"evaMaxMemUsage\":$evaMaxMemUsage,\"nodeLoadAvg\":\"$nodeLoadAvg\"}" +fi \ No newline at end of file diff --git a/web/src/main/resources/evaluate/execStepOut.sh b/web/src/main/resources/evaluate/execStepOut.sh new file mode 100644 index 0000000..1efa7e0 --- /dev/null +++ b/web/src/main/resources/evaluate/execStepOut.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +evaLog="/data/workspace/eva.log" +stepMsg=$evaLog + +evaTmpLog="/data/workspace/evaTmp.log" +evaStepMsg="{\"step\":" +export evaTmpLog +export evaStepMsg + +sendMsg() { + if [ "$2" ]; + then + echo "{\"step\":\"$1\",\"ratio\":$2}" + else + echo "{\"step\":\"$1\"}" + fi +} + +sendCaseMsg() { + caseTotal=${#ins[@]} + let caseNum=$1+1 + if [[ $caseTotal -eq $caseNum ]]; + then + return + fi + caseInfo="共$caseTotal个测试集,第$caseNum个测试集执行完成" + if [ "$2" ]; + then + let caseRatio=(100-$2)+$caseNum*$2/$caseTotal + echo "{\"step\":\"$caseInfo\",\"ratio\":$caseRatio}" + else + echo "{\"step\":\"$caseInfo\"}" + fi +} + +# 更新业务程序执行信息 +getEvaPidInfo() { + evaPid=$(ps -p $eva_pid | grep $eva_pid) +} + +#按行读取文件 +readLine(){ + linenumThisTime=0 + readedLine=-1 + while read LINE + do + let linenumThisTime=linenumThisTime+1 + if [[ $linenumThisTime -lt $linenum ]]; + then + continue + else + curline="$LINE" + readedLine=0 + break + fi + done <$evaLog +} + +#处理每行信息 +processLine() { + if [ $readedLine -eq 0 ];then + match=$(echo $curline |grep -e "{\"step\":.*}") + if [[ ! ${#match} -eq 0 ]] + then + echo $curline + fi + let linenum=linenum+1 + fi +} + +#按行读取临时文件 +readLineTmp(){ + linenumThisTimeTmp=0 + readedLineTmp=-1 + while read LINE + do + let linenumThisTimeTmp=linenumThisTimeTmp+1 + if [[ $linenumThisTimeTmp -lt $linenumTmp ]]; + then + continue + else + curlineTmp="$LINE" + readedLineTmp=0 + break + fi + done <$evaTmpLog +} + +#处理每行信息 +processLineTmp() { + if [ $readedLineTmp -eq 0 ];then + match=$(echo $curlineTmp |grep -e "{\"step\":.*}") + if [[ ! ${#match} -eq 0 ]]; + then + echo $curlineTmp + fi + let linenumTmp=linenumTmp+1 + fi +} + +handEvaTmpLog() { + if [ -s $evaTmpLog ]; + then + readLineTmp + processLineTmp + fi +} + +#获取结果 +getResult() { + linenum=1 + linenumTmp=1 + readedLineTmp=-1 + getEvaPidInfo + while : + do + if [ "$evaPid" ] + then + if [ -s $evaLog ] + then + #进程存在,并且日志文件已经写入内容,结束循环,后面去读取文件 + break + else + #进程存在,但日志文件不存在,等待进程创建文件,并写入内容 + sleep 1s + getEvaPidInfo + fi + else + if [ -s $evaLog ] + then + #进程不存在,但日志文件已经写入内容,结束循环,后面去读取文件。 + #这种情况是进程很快执行完成,正常情况下不会发生,可能是发生了错误。 + break + else + #进程不存在,日志文件也不存在,可能是进程还没有开始执行。 + #这种情况应该不存在,也可能进程发生了错误,不再执行,这样会一直持续到超时。 + sleep 1s + getEvaPidInfo + fi + fi + done + + #程序执行中,读取日志 + getEvaPidInfo + while [ "$evaPid" ] + do + readLine + processLine + handEvaTmpLog + if [[ ! $readedLine -eq 0 ]] && [[ ! $readedLineTmp -eq 0 ]];then + sleep 1s + fi + getEvaPidInfo + done + + #程序执行完成,读取evaTmpLog剩余分步消息 + handEvaTmpLog + while [[ $readedLineTmp -eq 0 ]] + do + handEvaTmpLog + done + + #程序执行完成,读取evaLog剩余分步消息 + readLine + while [[ $readedLine -eq 0 ]] + do + processLine + readLine + done + + #最后结果 + if [ "$1" ]; + then + result=$(cat $evaLog | grep -v $evaStepMsg) + else + result=$(cat $evaLog | grep -v $evaStepMsg | base64) + fi +} + +#清空evaLog文件,不能删除该文件,因为当evaTmpLog文件先输入信息时,会造成未能及时读取evaTmpLog文件的信息 +echo '' > $evaLog +rm -rf $evaTmpLog +#清空用户日志输出文件 +rm -f /data/workspace/user.out +source /data/protectspace/evaluate.sh $1 $2 $3 $4 $5 \ No newline at end of file diff --git a/web/src/main/resources/evaluate/kill.sh b/web/src/main/resources/evaluate/kill.sh new file mode 100644 index 0000000..bab45d9 --- /dev/null +++ b/web/src/main/resources/evaluate/kill.sh @@ -0,0 +1,14 @@ +# kill -9 `ps -ef | grep data | grep -v grep | awk -F' ' '{print $2}' ` +list_descendants () +{ + local children=$(ps -o pid= --ppid "$1") + + for pid in $children + do + list_descendants "$pid" + done + + echo "$children" +} + +kill $(list_descendants `ps -ef | grep execEva | grep -v grep | awk -F' ' '{print $2}'`) \ No newline at end of file diff --git a/web/src/main/resources/logback-spring.xml b/web/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..d44e56b --- /dev/null +++ b/web/src/main/resources/logback-spring.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n + + + + + + + + + ${LOG_HOME}/${appName}.log + + + + ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log + + 7 + + + 100MB + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n + + + + + + ${LOG_HOME}/debug.log + true + + ${LOG_HOME}/debug/%d{yyyy-MM-dd}/debug.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + DEBUG + ACCEPT + DENY + + + + + ${LOG_HOME}/warn.log + true + + ${LOG_HOME}/warn/%d{yyyy-MM-dd}/warn.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + WARN + ACCEPT + DENY + + + + + ${LOG_HOME}/error.log + true + + ${LOG_HOME}/error/%d{yyyy-MM-dd}/error.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + ERROR + ACCEPT + DENY + + + + + ${LOG_HOME}/trace.log + true + + ${LOG_HOME}/trace/%d{yyyy-MM-dd}/trace.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + TRACE + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/target/classes/application.properties b/web/target/classes/application.properties new file mode 100644 index 0000000..24d1967 --- /dev/null +++ b/web/target/classes/application.properties @@ -0,0 +1,56 @@ +server.port=8088 +server.servlet.context-path=/openi +server.max-http-header-size=8192 +server.compression.enabled=true +server.compression.min-response-size=1024 +server.compression.mime-types=text/plain,text/css,text/xml,text/javascript,application/json,application/javascript,application/xml,application/xml+rss,application/x-javascript,application/x-httpd-php,image/jpeg,image/gif,image/png +server.tomcat.uri-encoding=UTF-8 + + + +spring.config.import=classpath:common.properties + + + +# mysql +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://rm-bp13v5020p7828r5rso.mysql.rds.aliyuncs.com:3306/imitate_sys?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false +spring.datasource.username=testeducoder +spring.datasource.password=TEST@123 +spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +spring.datasource.initialSize=200 +spring.datasource.maxActive=400 +spring.datasource.minIdle=200 +spring.datasource.validationQuery=select 1 +spring.datasource.testOnBorrow=false +spring.datasource.testOnReturn=false +spring.datasource.testWhileIdle=true +mybatis.type-aliases-package=com.imitate.web.persistence.beans +mybatis.mapperLocations=classpath*:mybatis/*.xml +mybatis.configuration.map-underscore-to-camel-case=true + +#mapper +mapper.mappers=com.imitate.common.util.BaseMapper +mapper.not-empty=false +mapper.identity=MYSQL + + +# pagehelper +pagehelper.helper-dialect=mysql +pagehelper.reasonable=true +pagehelper.support-methods-arguments=true +pagehelper.params=count=countSql + + +#showSql +logging.level.com.imitate.web.persistence.mapper=debug + + +# redis +spring.redis.host=127.0.0.1 +spring.redis.port=6379 +spring.redis.password= + + + + diff --git a/web/target/classes/evaluate/evaluate.sh b/web/target/classes/evaluate/evaluate.sh new file mode 100644 index 0000000..752a17b --- /dev/null +++ b/web/target/classes/evaluate/evaluate.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# 待执行的评测文件 +sourceClassNames="$3" + +# 执行命令 +executeCommand="python3" + +# 获取测试用例的输入(请勿改动此行语句) +input=$2; OLD_IFS="$IFS"; IFS=,; ins=($input); IFS="$OLD_IFS" + +compileResult=$(echo -n "compile successfully" | base64) + +# 执行函数 +execute(){ + # 当前关卡的执行目标文件 + sourceClassName=${sourceClassNames[$1 - 1]} + # 当前关卡号 + challengeStage=$1 + # 循环获取各测试用例下的实际输出 + res_usage="{\"testSetUsages\":[" + output='' + i=0 + while [[ i -lt ${#ins[*]} ]]; do + #echo 0 > /sys/fs/cgroup/memory/memory.max_usage_in_bytes + startCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + result=$(echo "${ins[$i]}" | base64 -d | $executeCommand $sourceClassName 2>&1 | tee /data/workspace/user.out | base64) + #拼接输出结果 + endCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + let testSetCpuUsage=$endCpuUsage-$startCpuUsage + maxMemUsage=$(cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes) + res_usage="$res_usage{\"testSetTime\":\"$testSetCpuUsage\",\"testSetMem\":\"$maxMemUsage\"}," + output=$output\"$result\", + let i++ + done + output="[${output%?}]" +} +execute $1 +res_usage="${res_usage::-1}" +res_usage="$res_usage]}" +res_usage=$(echo -ne "$res_usage"|base64) + +# 返回评测结果 +returnResult(){ + result="{\"compileResult\":\"$compileResult\",\"out\":$output}" + echo $result +} +returnResult \ No newline at end of file diff --git a/web/target/classes/evaluate/execEva.py b/web/target/classes/evaluate/execEva.py new file mode 100644 index 0000000..961bb18 --- /dev/null +++ b/web/target/classes/evaluate/execEva.py @@ -0,0 +1,105 @@ +# jupyter评测执行脚本 + +#!/usr/bin/python3 +# -*- coding:utf-8 -*- + +from pickle import TRUE +from re import I +import re +from tkinter.messagebox import RETRY +from click import command +from pathlib import Path +import sys +import base64 +import os +import json +import subprocess + + +class Evalute: + + def __init__(self,input,jupyter_file,timeout): + try: + f = open(jupyter_file) + f.close() + except FileNotFoundError: + print("Error: 没有找到文件 %s,%s",jupyter_file) + else: + #存在 + self.encode_input = input + self.jupyter_file = jupyter_file + self.decode_input = self.decode_case(input) + self.timeout = timeout + + + + #解析测试集 + def decode_case(self,input): + input_list = [] + if input == "IA==": + input_list.append(input) + else: + #判断整个base64是否包含逗号,若包含则切割,不包含则解析返回 + if input.find(",") != -1: + input_list = [] + input_list = input.split(",") + else: + input_list.append(input) + return input_list + + + #检查测试集是否为空 + def check_input_is_null(self): + #假设无测试集的情况是空格 + if self.decode_input[0] == "IA==": + return True + return False + + + #初始化,生成执行命令列表 + def init(self): + self.cmd_list = [] + if self.check_input_is_null() == False: + for param in self.decode_input: + str = "echo \"" + param + "\" | base64 -d |" + "/usr/bin/python3.6 " + self.jupyter_file + " 2>&1 | base64" + self.cmd_list.append(str) + if self.check_input_is_null() == True: + str = "/usr/bin/python3 " + self.jupyter_file + " 2>&1 |base64" + self.cmd_list.append(str) + return + + #执行评测并保存结果返回列表 + def run(self): + #评测结果 + self.eva_result = "" + r = [] + eva_result_direct = {} + eva_result_direct["compileResult"] = "Y29tcGlsZSBzdWNjZXNzZnVsbHk" + for num in self.cmd_list: + ret = os.popen(num,'r').read() + r.append(ret) + eva_result_direct["out"] = r + self.eva_result = json.dumps(eva_result_direct, ensure_ascii=False) + + + # 脚本只做结果输出 + def print_console(self): + print(self.eva_result) + return + + + #开始评测 + def start(self): + #评测初始化 + self.init() + #执行存结果 + self.run() + #输出结果到console + self.print_console() + + + +if __name__ == '__main__': + # 参数:测试集每组以逗号分隔base64编码;jupyter源代码路径;timeout; + a = Evalute(sys.argv[1],sys.argv[2],sys.argv[3]) + a.start() \ No newline at end of file diff --git a/web/target/classes/evaluate/execEva.sh b/web/target/classes/evaluate/execEva.sh new file mode 100644 index 0000000..2a0876b --- /dev/null +++ b/web/target/classes/evaluate/execEva.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +startCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) +timeout $1 bash /data/platform/eva/execStepOut.sh $2 $3 $4 $5 $6 +exitStatus=$? +if [ $exitStatus -eq 124 ] || [ $exitStatus -eq 137 ];then + endCpuUsage=$(cat /sys/fs/cgroup/cpuacct/cpuacct.usage) + let evaCpuUsage=$endCpuUsage-$startCpuUsage + evaMaxMemUsage=$(cat /sys/fs/cgroup/memory/memory.max_usage_in_bytes) + nodeLoadAvg=$(cat /proc/loadavg) + echo "{\"exitStatus\":$exitStatus,\"evaCpuUsage\":$evaCpuUsage,\"evaMaxMemUsage\":$evaMaxMemUsage,\"nodeLoadAvg\":\"$nodeLoadAvg\"}" +fi \ No newline at end of file diff --git a/web/target/classes/evaluate/execStepOut.sh b/web/target/classes/evaluate/execStepOut.sh new file mode 100644 index 0000000..1efa7e0 --- /dev/null +++ b/web/target/classes/evaluate/execStepOut.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +evaLog="/data/workspace/eva.log" +stepMsg=$evaLog + +evaTmpLog="/data/workspace/evaTmp.log" +evaStepMsg="{\"step\":" +export evaTmpLog +export evaStepMsg + +sendMsg() { + if [ "$2" ]; + then + echo "{\"step\":\"$1\",\"ratio\":$2}" + else + echo "{\"step\":\"$1\"}" + fi +} + +sendCaseMsg() { + caseTotal=${#ins[@]} + let caseNum=$1+1 + if [[ $caseTotal -eq $caseNum ]]; + then + return + fi + caseInfo="共$caseTotal个测试集,第$caseNum个测试集执行完成" + if [ "$2" ]; + then + let caseRatio=(100-$2)+$caseNum*$2/$caseTotal + echo "{\"step\":\"$caseInfo\",\"ratio\":$caseRatio}" + else + echo "{\"step\":\"$caseInfo\"}" + fi +} + +# 更新业务程序执行信息 +getEvaPidInfo() { + evaPid=$(ps -p $eva_pid | grep $eva_pid) +} + +#按行读取文件 +readLine(){ + linenumThisTime=0 + readedLine=-1 + while read LINE + do + let linenumThisTime=linenumThisTime+1 + if [[ $linenumThisTime -lt $linenum ]]; + then + continue + else + curline="$LINE" + readedLine=0 + break + fi + done <$evaLog +} + +#处理每行信息 +processLine() { + if [ $readedLine -eq 0 ];then + match=$(echo $curline |grep -e "{\"step\":.*}") + if [[ ! ${#match} -eq 0 ]] + then + echo $curline + fi + let linenum=linenum+1 + fi +} + +#按行读取临时文件 +readLineTmp(){ + linenumThisTimeTmp=0 + readedLineTmp=-1 + while read LINE + do + let linenumThisTimeTmp=linenumThisTimeTmp+1 + if [[ $linenumThisTimeTmp -lt $linenumTmp ]]; + then + continue + else + curlineTmp="$LINE" + readedLineTmp=0 + break + fi + done <$evaTmpLog +} + +#处理每行信息 +processLineTmp() { + if [ $readedLineTmp -eq 0 ];then + match=$(echo $curlineTmp |grep -e "{\"step\":.*}") + if [[ ! ${#match} -eq 0 ]]; + then + echo $curlineTmp + fi + let linenumTmp=linenumTmp+1 + fi +} + +handEvaTmpLog() { + if [ -s $evaTmpLog ]; + then + readLineTmp + processLineTmp + fi +} + +#获取结果 +getResult() { + linenum=1 + linenumTmp=1 + readedLineTmp=-1 + getEvaPidInfo + while : + do + if [ "$evaPid" ] + then + if [ -s $evaLog ] + then + #进程存在,并且日志文件已经写入内容,结束循环,后面去读取文件 + break + else + #进程存在,但日志文件不存在,等待进程创建文件,并写入内容 + sleep 1s + getEvaPidInfo + fi + else + if [ -s $evaLog ] + then + #进程不存在,但日志文件已经写入内容,结束循环,后面去读取文件。 + #这种情况是进程很快执行完成,正常情况下不会发生,可能是发生了错误。 + break + else + #进程不存在,日志文件也不存在,可能是进程还没有开始执行。 + #这种情况应该不存在,也可能进程发生了错误,不再执行,这样会一直持续到超时。 + sleep 1s + getEvaPidInfo + fi + fi + done + + #程序执行中,读取日志 + getEvaPidInfo + while [ "$evaPid" ] + do + readLine + processLine + handEvaTmpLog + if [[ ! $readedLine -eq 0 ]] && [[ ! $readedLineTmp -eq 0 ]];then + sleep 1s + fi + getEvaPidInfo + done + + #程序执行完成,读取evaTmpLog剩余分步消息 + handEvaTmpLog + while [[ $readedLineTmp -eq 0 ]] + do + handEvaTmpLog + done + + #程序执行完成,读取evaLog剩余分步消息 + readLine + while [[ $readedLine -eq 0 ]] + do + processLine + readLine + done + + #最后结果 + if [ "$1" ]; + then + result=$(cat $evaLog | grep -v $evaStepMsg) + else + result=$(cat $evaLog | grep -v $evaStepMsg | base64) + fi +} + +#清空evaLog文件,不能删除该文件,因为当evaTmpLog文件先输入信息时,会造成未能及时读取evaTmpLog文件的信息 +echo '' > $evaLog +rm -rf $evaTmpLog +#清空用户日志输出文件 +rm -f /data/workspace/user.out +source /data/protectspace/evaluate.sh $1 $2 $3 $4 $5 \ No newline at end of file diff --git a/web/target/classes/evaluate/kill.sh b/web/target/classes/evaluate/kill.sh new file mode 100644 index 0000000..bab45d9 --- /dev/null +++ b/web/target/classes/evaluate/kill.sh @@ -0,0 +1,14 @@ +# kill -9 `ps -ef | grep data | grep -v grep | awk -F' ' '{print $2}' ` +list_descendants () +{ + local children=$(ps -o pid= --ppid "$1") + + for pid in $children + do + list_descendants "$pid" + done + + echo "$children" +} + +kill $(list_descendants `ps -ef | grep execEva | grep -v grep | awk -F' ' '{print $2}'`) \ No newline at end of file diff --git a/web/target/classes/logback-spring.xml b/web/target/classes/logback-spring.xml new file mode 100644 index 0000000..d44e56b --- /dev/null +++ b/web/target/classes/logback-spring.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n + + + + + + + + + ${LOG_HOME}/${appName}.log + + + + ${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log + + 7 + + + 100MB + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n + + + + + + ${LOG_HOME}/debug.log + true + + ${LOG_HOME}/debug/%d{yyyy-MM-dd}/debug.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + DEBUG + ACCEPT + DENY + + + + + ${LOG_HOME}/warn.log + true + + ${LOG_HOME}/warn/%d{yyyy-MM-dd}/warn.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + WARN + ACCEPT + DENY + + + + + ${LOG_HOME}/error.log + true + + ${LOG_HOME}/error/%d{yyyy-MM-dd}/error.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + ERROR + ACCEPT + DENY + + + + + ${LOG_HOME}/trace.log + true + + ${LOG_HOME}/trace/%d{yyyy-MM-dd}/trace.%i.log.zip + 50MB + 7 + 20GB + + + [lf-1][${SERVER_NAME}][%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5level][%thread][%file:%line] - %msg%n + + + + TRACE + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/web.iml b/web/web.iml new file mode 100644 index 0000000..280680e --- /dev/null +++ b/web/web.iml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file