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