Ver Fonte

Merge branch 'platform-dev'

GuardOfDawn há 7 anos atrás
pai
commit
d075e31b88
77 ficheiros alterados com 3905 adições e 318 exclusões
  1. 11 2
      config.yml
  2. 34 0
      src/main/java/nju/seec/SEECdemo/data/entity/ResourceConfig.java
  3. 45 4
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/DeploymentApi.java
  4. 37 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/ExecApi.java
  5. 36 2
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/IngressApi.java
  6. 65 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/LimitRangeApi.java
  7. 14 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/LogApi.java
  8. 75 10
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/NamespaceApi.java
  9. 54 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/ResourceQuotaApi.java
  10. 88 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/SecretApi.java
  11. 38 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/ServiceApi.java
  12. 68 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/exception/K8sApiException.java
  13. 76 33
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/DeploymentApiImpl.java
  14. 66 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ExecApiImpl.java
  15. 119 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/IngressApiImpl.java
  16. 108 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/LimitRangeApiImpl.java
  17. 42 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/LogApiImpl.java
  18. 195 19
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/NamespaceApiImpl.java
  19. 169 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ResourceQuotaImpl.java
  20. 146 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/SecretApiImpl.java
  21. 101 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ServiceApiImpl.java
  22. 10 8
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Container.java
  23. 5 6
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ContainerPort.java
  24. 59 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/CpuQuantity.java
  25. 24 38
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Deployment.java
  26. 118 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Ingress.java
  27. 153 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/LabelSelector.java
  28. 66 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Namespace.java
  29. 84 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ResourceQuota.java
  30. 50 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Service.java
  31. 11 7
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ServicePort.java
  32. 64 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/StorageQuantity.java
  33. 9 23
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/BeanAnnouncement.java
  34. 41 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/SecretTypeEnum.java
  35. 14 1
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ApiResult.java
  36. 0 32
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Constants.java
  37. 83 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/LimitRange.java
  38. 4 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/LimitRangeVO.java
  39. 0 65
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Namespace.java
  40. 43 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/SecretVO.java
  41. 8 0
      src/main/java/nju/seec/SEECdemo/logic/exception/ServiceException.java
  42. 10 6
      src/main/java/nju/seec/SEECdemo/logic/service/ApplicationService.java
  43. 37 0
      src/main/java/nju/seec/SEECdemo/logic/service/DBService.java
  44. 6 2
      src/main/java/nju/seec/SEECdemo/logic/service/ProjectService.java
  45. 130 7
      src/main/java/nju/seec/SEECdemo/logic/service/impl/ApplicationServiceImpl.java
  46. 122 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/DBServiceImpl.java
  47. 129 8
      src/main/java/nju/seec/SEECdemo/logic/service/impl/ProjectServiceImpl.java
  48. 7 3
      src/main/java/nju/seec/SEECdemo/logic/vo/ApplicationVO.java
  49. 1 2
      src/main/java/nju/seec/SEECdemo/logic/vo/ContainerVO.java
  50. 24 0
      src/main/java/nju/seec/SEECdemo/logic/vo/DBDetailVO.java
  51. 29 0
      src/main/java/nju/seec/SEECdemo/logic/vo/PortVO.java
  52. 10 4
      src/main/java/nju/seec/SEECdemo/logic/vo/ProjectVO.java
  53. 8 0
      src/main/java/nju/seec/SEECdemo/logic/vo/WebApplicationVO.java
  54. 2 6
      src/main/java/nju/seec/SEECdemo/logic/vo/projecttemplate/ProjectTemplate.java
  55. 24 0
      src/main/java/nju/seec/SEECdemo/util/ApplicationProperties.java
  56. 33 0
      src/main/java/nju/seec/SEECdemo/util/BeanUtil.java
  57. 42 0
      src/main/java/nju/seec/SEECdemo/util/Constants.java
  58. 54 0
      src/main/java/nju/seec/SEECdemo/util/DateUtil.java
  59. 5 0
      src/main/java/nju/seec/SEECdemo/util/LoggerUtil.java
  60. 23 0
      src/main/java/nju/seec/SEECdemo/util/RandomStringUtil.java
  61. 43 2
      src/main/java/nju/seec/SEECdemo/web/dto/ApplicationDTO.java
  62. 13 2
      src/main/java/nju/seec/SEECdemo/web/dto/ContainerDTO.java
  63. 42 3
      src/main/java/nju/seec/SEECdemo/web/dto/PortDTO.java
  64. 10 1
      src/main/resources/application-dev.yml
  65. 48 0
      src/test/java/nju/seec/SEECdemo/service/ApplicationServiceImplTest.java
  66. 26 0
      src/test/java/nju/seec/SEECdemo/service/DbServiceImplTest.java
  67. 30 0
      src/test/java/nju/seec/SEECdemo/service/ProjectServiceImplTest.java
  68. 12 19
      src/test/java/nju/seec/SEECdemo/service/api/DeploymentApiImplTest.java
  69. 33 0
      src/test/java/nju/seec/SEECdemo/service/api/ExecApiImplTest.java
  70. 47 0
      src/test/java/nju/seec/SEECdemo/service/api/IngressApiImplTest.java
  71. 152 0
      src/test/java/nju/seec/SEECdemo/service/api/LimitRangeTest.java
  72. 28 0
      src/test/java/nju/seec/SEECdemo/service/api/LogApiImplTest.java
  73. 33 3
      src/test/java/nju/seec/SEECdemo/service/api/NamespaceApiImplTest.java
  74. 37 0
      src/test/java/nju/seec/SEECdemo/service/api/ResourceQuotaApiImplTest.java
  75. 118 0
      src/test/java/nju/seec/SEECdemo/service/api/SecretApiImpl.java
  76. 48 0
      src/test/java/nju/seec/SEECdemo/service/api/SecretApiTest.java
  77. 56 0
      src/test/java/nju/seec/SEECdemo/service/api/model/LabelSelectorTest.java

+ 11 - 2
config.yml

@@ -24,7 +24,7 @@ moocoder:
     username: root
     token: 113cb5bd4a6fea342892a08c8d2e07dc68
   docker:
-    nexus: 10.1.1.243:10082
+    nexus: 10.1.1.243:18082
   mail:
     from: noreply@moocoder.cn
   storage:
@@ -34,4 +34,13 @@ moocoder:
   k8s:
     api-server: https://10.1.1.198:6443
     token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi0yZ2o5NSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjRjZGQ0MGFlLTA1OWItMTFlOS1hMTIwLTAyMDA2MTI3MDAwNSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.LwQ-LQlA_yhtndGLpveRfs0ZFFW971P1tchzJZP81s1vz0msb0q6dLJmcsCHaYhHWZ58_DCc0e7suGmUFEeMZcJL-sj3js1Cp5WNdnyz99Y3vZ-krHvNHGoc42jOrNUeO27Kb7uTDCmmKcCJSQ9gw8s2ie--7Z_kPAKVjhqr9nHtfrmmuIqFgwbIBQrNyJLMxaXs6tsCS4YWBuIQK1aOPWsijJNXA8W1acQ7p95P3tBGYMq17AaOEdeK8m76CIWeIt1mzRmjAhWQ--06J2-zw1Xq62ogjQLnjZuS8Tq7G_wLeoAVo9llR72FNDBXuJo-eSdVKBrFP6-Q-nCaWcQkCw
-    ingress-host: deernowl.cn
+    ingress-host: deernowl.cn
+    public-namespace: group24
+    public-mysql-url: jdbc:mysql://mysql.group24.svc.cluster.local:3306
+    public-mysql-pod: mysql-7b688cf578-l7pxs
+    public-mysql-username: root
+    public-mysql-password: password
+  registry:
+    host: 10.1.1.243:18082
+    username: admin
+    password: admin123

+ 34 - 0
src/main/java/nju/seec/SEECdemo/data/entity/ResourceConfig.java

@@ -0,0 +1,34 @@
+package nju.seec.SEECdemo.data.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import nju.seec.SEECdemo.logic.api.k8s.model.CpuQuantity;
+import nju.seec.SEECdemo.logic.api.k8s.model.StorageQuantity;
+
+/**
+ * @Todo 是否需要将不同的配置新建为不同的类
+ */
+@Data
+@EqualsAndHashCode
+public class ResourceConfig {
+
+    /*项目的最大CPU配额*/
+    private CpuQuantity maxCPU;
+
+    /*单个组件的默认CPU配额*/
+    private CpuQuantity defaultCPU;
+
+    /*项目的最大内存配额*/
+    private StorageQuantity maxMemory;
+
+    /*项目中单个应用的默认内存配额*/
+    private StorageQuantity defaultMemory;
+
+    /*项目的最大卷空间*/
+    private StorageQuantity maxStorage;
+
+    /*项目中单个应用的默认卷空间*/
+    private StorageQuantity defaultStorage;
+
+}
+

+ 45 - 4
src/main/java/nju/seec/SEECdemo/logic/api/k8s/DeploymentApi.java

@@ -1,7 +1,6 @@
 package nju.seec.SEECdemo.logic.api.k8s;
 
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Deployment;
+import nju.seec.SEECdemo.logic.api.k8s.model.Deployment;
 
 /**
  * author: rale
@@ -9,7 +8,49 @@ import nju.seec.SEECdemo.logic.api.k8s.vo.Deployment;
  */
 public interface DeploymentApi {
 
-    ApiResult<Void> createSync(Deployment deployment);
+    /**
+     * 同步创建Deployment
+     * @param deployment
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND namespace不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException ALREADY_EXIST deployment已经存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    Deployment createSync(Deployment deployment);
+
+    /**
+     * 异步创建Deployment
+     * @param deployment
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND namespace不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException ALREADY_EXIST deployment已经存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    Deployment createAsync(Deployment deployment);
+
+    /**
+     * 删除deployment
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND namespace不存在/deployment不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void delete(String namespace, String name);
+
+
+    /**
+     * 删除deployment,如果该deployment不存在,则不执行任何操作,且不抛出异常
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void deleteIfExist(String namespace, String name);
+
+    /**
+     * 判断deployment是否存在
+     * @param namespace
+     * @param name
+     * @return 如果命名空间下deployment存在,则返回true,其它情况返回false
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    boolean exists(String namespace, String name);
 
-    ApiResult<Void> createAsync();
 }

+ 37 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/ExecApi.java

@@ -0,0 +1,37 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+
+import java.util.List;
+
+/**
+ * 进入容器执行指令
+ *
+ * @todo 传入输入流,获得输出流
+ */
+public interface ExecApi {
+
+    /**
+     * 选择namespace下podName中的containerName的容器执行commands指令
+     * @param namespace
+     * @param podName
+     * @param containerName
+     * @param commands
+     * @return exitCode=0 则返回true,其它情况返回false
+     * @throws K8sApiException NOT_FOUND 命名空间/pod/container不存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    boolean exec(String namespace, String podName, String containerName, List<String> commands);
+
+    /**
+     * 当且仅当pod下只有一个container时,该指令才会执行成功,否则会抛出异常
+     * @param namespace
+     * @param podName
+     * @param commands
+     * @return exitCode=0 则返回true,其它情况返回false
+     * @throws K8sApiException NOT_FOUND 命名空间不存在/pod不存在/pod下没有container
+     * @throws K8sApiException SYSTEM_ERROR pod存在多个container/系统异常/指令执行异常
+     */
+    boolean exec(String namespace, String podName, List<String> commands);
+}

+ 36 - 2
src/main/java/nju/seec/SEECdemo/logic/api/k8s/IngressApi.java

@@ -1,6 +1,6 @@
 package nju.seec.SEECdemo.logic.api.k8s;
 
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress;
 
 /**
  * author: rale
@@ -8,5 +8,39 @@ import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
  */
 public interface IngressApi {
 
-    ApiResult<String> create(String namespace, String name, String path, String serviceName, int port);
+    /**
+     * 创建Ingress资源
+     * @param ingress
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND 命名空间不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException ALREADY_EXIST ingress已经存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    Ingress create(Ingress ingress);
+
+    /**
+     * 删除namespace命名空间下的ingress
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND 命名空间/ingress不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void delete(String namespace, String name);
+
+    /**
+     * 删除namespace命名空间下的ingress
+     * 若命名空间不存在或者资源不存在,不会执行任何操作,也不会抛出异常
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void deleteIfExists(String namespace, String name);
+
+    /**
+     * 判断Ingress资源是否存在
+     * @param namespace
+     * @param name
+     * @return namespace下ingress存在则返回true,否则返回false
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    boolean exists(String namespace, String name);
 }

+ 65 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/LimitRangeApi.java

@@ -0,0 +1,65 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import io.kubernetes.client.models.V1LimitRange;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+
+import java.util.List;
+import java.util.Map;
+
+
+public interface LimitRangeApi {
+
+    /**
+     * 传入参数转换为V1LimitRange对象
+     *
+     * @param namespace 命名空间名称需要唯一
+     * @param name      LimitRange的资源名称
+     * @param _default  默认资源配额,存放资源名称和配额的键值对,String : cpu or memory (optional)
+     * @param defaultRequest 默认资源请求配额 (optional)
+     * @param max 最大资源配额 (optional)
+     * @param min 最小资源配额 (optional)
+     * note: 如果不需要optional的参数可传入null
+     */
+    V1LimitRange toLimitRange(String namespace, String name, Map<String, Integer> _default, Map<String, Integer> defaultRequest, Map<String, Integer> max, Map<String, Integer> min);
+
+    /**
+     * 传入参数转换为V1LimitRange对象
+     * @param namespace 命名空间名称需要唯一
+     * @param body      配置好的V1LimitRange,可由toLimitRange()获得
+     */
+    V1LimitRange createLimitRange(String namespace, V1LimitRange body) throws K8sApiException;
+
+    /**
+     * 删除命名空间内的指定名称的LimitRange
+     *
+     * @param namespace 命名空间名称需要唯一
+     * @param name      LimitRange的资源名称
+     */
+    void deleteLimitRange(String namespace, String name) throws K8sApiException;
+
+    /**
+     * 获取命名空间内的LimitRange列表
+     *
+     * @param namespace 命名空间名称需要唯一
+     */
+    List<V1LimitRange> getLimitRangeList(String namespace) throws K8sApiException;
+
+    /**
+     * 获取命名空间内的指定名称的LimitRange
+     *
+     * @param namespace 命名空间名称需要唯一
+     * @param name      LimitRange的资源名称
+     */
+    V1LimitRange getLimitRangeByName(String namespace, String name) throws K8sApiException;
+
+    /**
+     * 更新命名空间内的指定名称的LimitRange
+     *
+     * @param namespace 命名空间名称需要唯一
+     * @param name      LimitRange的资源名称
+     * @param body
+     */
+    V1LimitRange updateLimitRange(String namespace, String name, V1LimitRange body) throws K8sApiException;
+
+}
+

+ 14 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/LogApi.java

@@ -0,0 +1,14 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import io.kubernetes.client.ApiException;
+
+import java.io.IOException;
+
+/**
+ * author: rale
+ * createdAt: 1/6/19
+ */
+public interface LogApi {
+
+    void test() throws ApiException, IOException;
+}

+ 75 - 10
src/main/java/nju/seec/SEECdemo/logic/api/k8s/NamespaceApi.java

@@ -1,19 +1,84 @@
 package nju.seec.SEECdemo.logic.api.k8s;
 
-import io.kubernetes.client.models.V1Namespace;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
-
-/**
- * author: rale
- * createdAt: 12/22/18
- */
+import nju.seec.SEECdemo.logic.api.k8s.model.LabelSelector;
+import nju.seec.SEECdemo.logic.api.k8s.model.Namespace;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+
+import java.util.List;
+import java.util.Map;
+
 public interface NamespaceApi {
 
     /**
-     * 新建一个命名空间
+     * 新建命名空间
      * @param namespace 命名空间名称需要唯一
      * @return
+     * @throws K8sApiException 命名空间已经存在
+     * @throws K8sApiException 系统异常
+     */
+    Namespace create(Namespace namespace) throws K8sApiException;
+
+
+    /**
+     * 删除命名空间,如果命名空间不存在,则抛出异常
+     * @param name
+     * @return
+     * @throws K8sApiException 命名空间不存在
+     * @throws K8sApiException 系统异常
+     */
+    void delete(String name);
+
+    /**
+     * 删除命名空间,如果命名空间不存在,则不执行任何操作
+     * @param name
+     * @throws K8sApiException 系统异常
+     */
+    void deleteIfExists(String name);
+
+    /**
+     * 获得名称为name的命名空间
+     * @param name
+     * @return 如果不存在,则返回null
+     * @throws K8sApiException 系统异常
+     */
+    Namespace getByName(String name);
+
+    /**
+     * 获得所有命名空间
+     * @return
+     * @throws K8sApiException 系统异常
+     */
+    List<Namespace> getAll();
+
+    /**
+     * 获取匹配labels标签的namespace
+     * @param labels
+     * @return
+     * @throws K8sApiException 系统异常
+     */
+    List<Namespace> getByLabels(Map<String, String> labels);
+
+    /**
+     * 获取匹配了label标签的namespace
+     * @param key
+     * @param value
+     * @return
+     * @throws K8sApiException 系统异常
+     */
+    List<Namespace> getByLabel(String key, String value);
+
+    /**
+     * 使用标签选择器筛选Namespace
+     * @param labelSelector
+     * @return
+     */
+    List<Namespace> getByLabelSelector(LabelSelector labelSelector);
+
+    /**
+     * 判断该命名空间是否存在
+     * @param name
+     * @return 存在返回true,否则返回false
+     * @throws K8sApiException 系统异常
      */
-    ApiResult<Void> create(Namespace namespace);
+    boolean exists(String name);
 }

+ 54 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/ResourceQuotaApi.java

@@ -0,0 +1,54 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import nju.seec.SEECdemo.logic.api.k8s.model.ResourceQuota;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+
+public interface ResourceQuotaApi {
+
+    /**
+     * 新建资源限额,如果该命名空间下的限额已经存在,则更新限额
+     * @param resourceQuota
+     * @return
+     * @throws K8sApiException NOT_FOUND 命名空间不存在
+     * @throws K8sApiException ALREADY_EXIST 资源已经存在
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    ResourceQuota create(ResourceQuota resourceQuota);
+
+
+    /**
+     * 删除namespace下名为name的资源限额
+     * @param namespace
+     * @param name
+     * @return
+     * @throws K8sApiException NOT FOUND 命名空间不存在/资源限额不存在
+     * @throws K8sApiException 系统异常
+     */
+    void delete(String namespace, String name);
+
+    /**
+     * 删除namespace下名为name的资源限额, 如果资源限额不存在,则不执行任何操作
+     * @param namespace
+     * @param name
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void deleteIfExists(String namespace, String name);
+
+    /**
+     * 获取namespace下名为name的资源配额
+     * @param namespace
+     * @param name
+     * @return 如果该资源配额或者命名空间不存在,则返回null
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    ResourceQuota getByName(String namespace, String name);
+
+    /**
+     * 当前的资源限额是否存在
+     * @param namespace
+     * @param name
+     * @return 如果命名空间下资源配额存在,则返回true,否则返回false
+     * @throws K8sApiException SYSTEM_ERROR 系统异常
+     */
+    boolean exist(String namespace, String name);
+}

+ 88 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/SecretApi.java

@@ -0,0 +1,88 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import com.google.protobuf.Api;
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.models.V1Secret;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.util.SecretTypeEnum;
+import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+import nju.seec.SEECdemo.logic.api.k8s.vo.SecretVO;
+
+import javax.xml.transform.Result;
+import java.util.List;
+import java.util.Map;
+
+public interface SecretApi {
+
+    /**
+     * 创建通用密钥,如数据库的用户名,密码,默认type为Opaque
+     * @param namespace
+     * @param name
+     * @param data
+     * @return ApiResult
+     */
+    SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data) throws K8sApiException;
+
+    /**
+     * 创建通用密钥,自己定义Secret的type,如Service Account、Opaque、kubernetes.io/dockerconfigjson.
+     * @param namespace
+     * @param name
+     * @param data
+     * @param type
+     * @return ApiResult
+     */
+    SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data, SecretTypeEnum type) throws K8sApiException;
+
+    /**
+     * 在namespace下生成私有仓库server的密钥
+     * 密钥名称将自动生成
+     * @param namespace
+     * @param server
+     * @param user
+     * @param password
+     */
+    SecretVO createPrivateRegistrySecret(String namespace, String server, String user, String password) throws K8sApiException;
+
+
+    /**
+     * 在namespace下生成私有仓库server的密钥
+     * 密钥名称自己指定
+     * @param namespace
+     * @param name
+     * @param server
+     * @param user
+     * @param password
+     */
+    SecretVO createPrivateRegistrySecret(String namespace, String name, String server, String user, String password) throws K8sApiException;
+
+    /**
+     * 获取指定namespace下的Secret列表
+     * @param namespace
+     */
+    List<SecretVO> getSecretList(String namespace) throws K8sApiException;
+
+    /**
+     * 获取指定namespace下的指定type的Secret列表
+     *
+     * @param namespace
+     * @param type
+     */
+    List<SecretVO> getSecretListByType(String namespace, SecretTypeEnum type) throws K8sApiException;
+
+
+    /**
+     * 获取指定namespace下的密钥
+     * 返回指定命名的Secret
+     * @param namespace
+     * @param name
+     */
+    SecretVO getSecretByName(String namespace, String name) throws K8sApiException;
+
+    /**
+     * 删除指定namespace下指定名字的Secret
+     * @param namespace
+     * @param name
+     */
+    void deleteSecretByName(String namespace, String name) throws K8sApiException;
+
+}

+ 38 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/ServiceApi.java

@@ -0,0 +1,38 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import nju.seec.SEECdemo.logic.api.k8s.model.Service;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+public interface ServiceApi {
+
+    /**
+     * 创建Service
+     * @param service
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND namespace不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException ALREADY_EXISTS service已经存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void create(Service service);
+
+    /**
+     * 删除命名空间namespace下名为name的Service
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException NOT_FOUND 命名空间不存在/资源不存在
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void delete(String namespace, String name);
+
+    /**
+     * 删除命名空间namespace下名为name的Service
+     * 如果该资源不存在或者命名空间不存在,则不执行任何操作,也不抛出异常
+     * @param namespace
+     * @param name
+     * @throws nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException SYSTEM_ERROR 系统异常
+     */
+    void deleteIfExists(String namespace, String name);
+
+}

+ 68 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/exception/K8sApiException.java

@@ -0,0 +1,68 @@
+package nju.seec.SEECdemo.logic.api.k8s.exception;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+public class K8sApiException extends RuntimeException{
+
+    private int code;
+
+    private String description;
+
+    public static final Map<Integer, String> codeMap = new HashMap<>();
+
+    public static final int NAMESPACE_NOT_EXIST = 400;
+    public static final int DEPLOYMENT_NOT_EXIST = 401;
+    public static final int NOT_FOUND = 404;
+    public static final int RESOURCE_QUOTA_NOT_EXIST = 402;
+    public static final int ALREADY_EXIST = 409;
+    //LimitRange的参数配置:default值不大于max的值,default request的值不小于min的值
+    public static final int INVALID_VALUE = 422;
+
+    public static final int NAMESPACE_ALREADY_EXIST = 500;
+    public static final int RESOURCE_QUOTA_ALREADY_EXIST = 501;
+
+    public static final int ILLEGAL_PARAMETER = 100;
+
+    public static final int SYSTEM_ERROR = 200;
+
+    public static final int COMMAND_FAIL = 300;
+
+
+    public static final K8sApiException K8s_SYSTEM_ERROR_EXCEPTION = new K8sApiException(SYSTEM_ERROR);
+
+    public static final K8sApiException K8s_NAMESPACE_NOT_EXIST = new K8sApiException(NAMESPACE_NOT_EXIST);
+    //系统故障
+    static {
+        codeMap.put(NAMESPACE_NOT_EXIST, "命名空间不存在");
+        codeMap.put(DEPLOYMENT_NOT_EXIST, "部署不存在");
+        codeMap.put(NOT_FOUND, "资源不存在");
+        codeMap.put(RESOURCE_QUOTA_NOT_EXIST, "资源限额不存在");
+        codeMap.put(ALREADY_EXIST, "资源已存在");
+        codeMap.put(INVALID_VALUE, "参数设置无效");
+        codeMap.put(NAMESPACE_ALREADY_EXIST, "命名空间已经存在");
+        codeMap.put(RESOURCE_QUOTA_ALREADY_EXIST, "资源限额已经存在");
+
+        codeMap.put(ILLEGAL_PARAMETER, "非法参数");
+
+        codeMap.put(SYSTEM_ERROR, "系统异常");
+
+        codeMap.put(COMMAND_FAIL, "指令执行失败");
+    }
+    public K8sApiException(int code, String description){
+        this.code = code;
+        this.description = description;
+    }
+
+    public K8sApiException(int code) {
+        this.code = code;
+        this.description = codeMap.getOrDefault(code, "操作执行失败");
+    }
+
+
+}

+ 76 - 33
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/DeploymentApiImpl.java

@@ -2,19 +2,20 @@ package nju.seec.SEECdemo.logic.api.k8s.impl;
 
 import io.kubernetes.client.ApiException;
 import io.kubernetes.client.apis.AppsV1Api;
-import io.kubernetes.client.apis.CoreV1Api;
-import io.kubernetes.client.models.V1Deployment;
-import io.kubernetes.client.models.V1ReplicationController;
-import io.kubernetes.client.models.V1Service;
+import io.kubernetes.client.models.*;
 import nju.seec.SEECdemo.logic.api.k8s.DeploymentApi;
-import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Deployment;
+import nju.seec.SEECdemo.logic.api.k8s.model.Deployment;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
 import nju.seec.SEECdemo.util.LoggerUtil;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.NOT_FOUND;
+import static nju.seec.SEECdemo.util.Constants.FOREGROUND_PROPAGATION_POLICY;
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
 /**
  * author: rale
  * createdAt: 12/23/18
@@ -26,44 +27,86 @@ public class DeploymentApiImpl implements DeploymentApi{
     @Autowired
     private AppsV1Api appsV1Api;
 
-    @Autowired
-    private CoreV1Api coreV1Api;
-
-    @Autowired
-    private NamespaceApi namespaceApi;
-
     @Override
-    public ApiResult<Void> createSync(Deployment deployment) {
-        //@todo 判断命名空间是否存在
+    public Deployment createSync(Deployment deployment) {
+
         try {
             V1Deployment v1Deployment = deployment.toV1Deployment();
-            appsV1Api.createNamespacedDeployment(deployment.getNamespace(),v1Deployment, "true");
+            V1Deployment result = appsV1Api.createNamespacedDeployment(deployment.getNamespace(),v1Deployment, PRETTY_FORMAT);
+            return v1Deployment == null ? null : new Deployment(result);
         } catch (ApiException e) {
             LoggerUtil.error(logger, e, "deployment创建失败, deployment={}, response={}", deployment, e.getResponseBody());
-            ApiResult<Void> result = new ApiResult<>();
-            result.setSuccess(false);
-            result.setErrorCode(e.getCode());
-            return result;
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "namespace不存在");
+            } else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "deployment已经存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "deployment创建异常, deployment={}, response={}", deployment);
+        }
+        return null;
+    }
+
+    @Override
+    public Deployment createAsync(Deployment deployment) {
+        return null;
+    }
+
+    @Override
+    public void delete(String namespace, String name) {
+        try {
+
+            deleteDeployment(namespace, name);
+
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "删除Deployment失败,namespace={}, name={}, response={}", namespace, name, e.getResponseBody());
+
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "deployment不存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除Deployment异常,namespace={}, name={}", namespace, name);
         }
+    }
 
+    @Override
+    public void deleteIfExist(String namespace, String name) {
         try {
-            V1Service v1Service = deployment.toV1Service();
-            coreV1Api.createNamespacedService(deployment.getNamespace(), v1Service, "true");
+
+            deleteDeployment(namespace, name);
+
         } catch (ApiException e) {
-            LoggerUtil.error(logger, e, "service创建失败, service={}, response={}", deployment, e.getResponseBody());
-            //@todo 清理现场 删除deployment
-            ApiResult<Void> result = new ApiResult<>();
-            result.setSuccess(false);
-            result.setErrorCode(e.getCode());
-            return result;
+            if (e.getCode() != K8sApiException.NOT_FOUND) {
+                LoggerUtil.error(logger, e, "删除Deployment失败,namespace={}, name={}, response={}", namespace, name, e.getResponseBody());
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除Deployment异常,namespace={}, name={}", namespace, name);
         }
-        ApiResult<Void> result = new ApiResult<>();
-        result.setSuccess(true);
-        return result;
     }
 
     @Override
-    public ApiResult<Void> createAsync() {
-        return null;
+    public boolean exists(String namespace, String name) {
+        return false;
+    }
+
+    private void deleteDeployment(String namespace, String name) throws ApiException{
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Deployment.API_VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        appsV1Api.deleteNamespacedDeployment(
+                name,
+                namespace,
+                v1DeleteOptions,
+                PRETTY_FORMAT,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY);
     }
 }

+ 66 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ExecApiImpl.java

@@ -0,0 +1,66 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import com.google.common.io.ByteStreams;
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.Exec;
+import nju.seec.SEECdemo.logic.api.k8s.ExecApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.List;
+
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.NOT_FOUND;
+
+
+@Service
+public class ExecApiImpl implements ExecApi{
+
+    private static final Logger logger = LoggerUtil.getLogger(ExecApiImpl.class);
+
+    @Autowired
+    private Exec exec;
+
+
+    @Override
+    public boolean exec(String namespace, String podName, String containerName, List<String> commands) {
+        return false;
+    }
+
+    @Override
+    public boolean exec(String namespace, String podName, List<String> commands) throws K8sApiException{
+        try {
+            final Process proc =
+                    exec.exec(
+                            namespace,
+                            podName,
+                            commands.isEmpty()
+                                    ? new String[]{"sh"}
+                                    : commands.toArray(new String[commands.size()]),
+                            true,
+                            false);
+            proc.waitFor();
+            proc.destroy();
+            return proc.exitValue() == 0;
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}, response={}",
+                namespace, podName, commands, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "容器不存在");
+            }else{
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (InterruptedException | IOException e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}",
+                    namespace, podName, commands);
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "指令执行异常, namespace={}, podName={}, commands={}",
+                    namespace, podName, commands);
+        }
+        return false;
+    }
+}

+ 119 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/IngressApiImpl.java

@@ -0,0 +1,119 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.ExtensionsV1beta1Api;
+import io.kubernetes.client.models.*;
+import nju.seec.SEECdemo.logic.api.k8s.IngressApi;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.util.ApplicationProperties;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.NOT_FOUND;
+import static nju.seec.SEECdemo.util.Constants.FOREGROUND_PROPAGATION_POLICY;
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+@Service
+public class IngressApiImpl implements IngressApi{
+    private static final Logger logger = LoggerUtil.getLogger(IngressApi.class);
+
+    @Autowired
+    private final ExtensionsV1beta1Api extensionsV1beta1Api;
+
+    @Autowired
+    public IngressApiImpl(ApplicationProperties applicationProperties, ExtensionsV1beta1Api extensionsV1beta1Api){
+        this.extensionsV1beta1Api = extensionsV1beta1Api;
+    }
+
+    @Override
+    public Ingress create(Ingress ingress) {
+        V1beta1Ingress v1beta1Ingress = ingress.toV1beta1Ingress();
+
+        try {
+            V1beta1Ingress result = extensionsV1beta1Api.createNamespacedIngress(
+                    ingress.getNamespace(),
+                    v1beta1Ingress,
+                    PRETTY_FORMAT);
+            return result == null ? null : new Ingress(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress 创建失败,ingress={}, response={}", ingress, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "命名空间不存在");
+            }else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "ingress已经存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "ingress 创建异常,ingress={}", ingress);
+        }
+        return null;
+    }
+
+    @Override
+    public void delete(String namespace, String name) {
+        try {
+
+            deleteIngress(namespace, name);
+
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress 删除失败,name={}, namespace={}, response={}", name, namespace, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "ingress不存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            //K8s swagger 异常, 无需进行处理
+        }
+    }
+
+    @Override
+    public void deleteIfExists(String namespace, String name) {
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Ingress.API_VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        try {
+            extensionsV1beta1Api.deleteNamespacedIngress(name, namespace, v1DeleteOptions, PRETTY_FORMAT, null, null, FOREGROUND_PROPAGATION_POLICY);;
+        }catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "ingress 删除失败,name={}, namespace={}, response={}", name, namespace, e.getResponseBody());
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            //K8s Swagger 异常, 无需进行处理
+            LoggerUtil.error(logger, e, "ingress 删除异常,name={}, namespace={}", name, namespace);
+        }
+    }
+
+    //@todo
+    @Override
+    public boolean exists(String namespace, String name) {
+        return false;
+    }
+
+    private void deleteIngress(String namespace, String name) throws ApiException{
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Ingress.API_VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        extensionsV1beta1Api.deleteNamespacedIngress(
+                name,
+                namespace,
+                v1DeleteOptions,
+                PRETTY_FORMAT,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY);
+    }
+}

+ 108 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/LimitRangeApiImpl.java

@@ -0,0 +1,108 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1DeleteOptions;
+import io.kubernetes.client.models.V1LimitRange;
+import io.kubernetes.client.models.V1LimitRangeList;
+import io.kubernetes.client.models.V1Status;
+import nju.seec.SEECdemo.logic.api.k8s.LimitRangeApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.vo.LimitRange;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+
+@Service
+public class LimitRangeApiImpl implements LimitRangeApi {
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Override
+    public V1LimitRange toLimitRange(String namespace, String name, Map<String, Integer> _default, Map<String, Integer> defaultRequest, Map<String, Integer> max, Map<String, Integer> min) {
+        LimitRange limitRange = new LimitRange();
+        limitRange.setName(name);
+        limitRange.setNamespace(namespace);
+        limitRange.set_default(_default);
+        limitRange.setDefault_request(defaultRequest);
+        limitRange.setMax(max);
+        limitRange.setMin(min);
+        return limitRange.toV1LimitRange();
+    }
+
+    @Override
+    public V1LimitRange createLimitRange(String namespace, V1LimitRange body) throws K8sApiException {
+
+        try {
+            return coreV1Api.createNamespacedLimitRange(namespace, body, "OK");
+        } catch (ApiException e) {
+            //namespace NotFound 404
+            //Already Exist 409
+            //422 if default value greater than max value, or min value greater than defaultRequest value
+            switch (e.getCode()) {
+                case K8sApiException.NOT_FOUND:
+                    throw new K8sApiException(K8sApiException.NOT_FOUND);
+                case K8sApiException.ALREADY_EXIST:
+                    throw new K8sApiException(K8sApiException.ALREADY_EXIST);
+                case K8sApiException.INVALID_VALUE:
+                    throw new K8sApiException(K8sApiException.INVALID_VALUE);
+                default:
+                    throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public void deleteLimitRange(String namespace, String name) throws K8sApiException {
+        V1DeleteOptions body = new V1DeleteOptions();
+        try {
+            //暂时没有用到deleteNamespacedSecret()中的其他参数。
+            V1Status v1Status = coreV1Api.deleteNamespacedLimitRange(name, namespace, body, "", 5, false, "");
+        } catch (ApiException e) {
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public List<V1LimitRange> getLimitRangeList(String namespace) throws K8sApiException {
+        try {
+            V1LimitRangeList limitRangeList = coreV1Api.listNamespacedLimitRange(namespace, "OK", "", "", false, "", 5, "", 5, false);
+            List<V1LimitRange> result = limitRangeList.getItems();
+            if (result.isEmpty()) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND);
+            } else {
+                return result;
+            }
+        } catch (ApiException e) {
+            //如果namespace不存在也不会抛出异常,会返回一个空的list
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+
+    }
+
+    @Override
+    public V1LimitRange getLimitRangeByName(String namespace, String name) throws K8sApiException {
+        List<V1LimitRange> list = getLimitRangeList(namespace);
+        for (V1LimitRange limitRange : list) {
+            if (limitRange.getMetadata().getName().equals(name)) {
+                return limitRange;
+            }
+        }
+        throw new K8sApiException(K8sApiException.NOT_FOUND);
+    }
+
+    @Override
+    public V1LimitRange updateLimitRange(String namespace, String name, V1LimitRange body) throws K8sApiException {
+        //暂时没研究清楚Api参数,该方法实现为删除并重新创建
+        deleteLimitRange(namespace, name);
+        return createLimitRange(namespace, body);
+    }
+}

+ 42 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/LogApiImpl.java

@@ -0,0 +1,42 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import com.google.common.io.ByteStreams;
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.PodLogs;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1Pod;
+import nju.seec.SEECdemo.logic.api.k8s.LogApi;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+
+/**
+ * author: rale
+ * createdAt: 1/6/19
+ */
+@Service
+public class LogApiImpl implements LogApi{
+
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    public LogApiImpl(CoreV1Api coreV1Api) {
+        this.coreV1Api = coreV1Api;
+    }
+
+    @Override
+    public void test() throws ApiException, IOException {
+        V1Pod pod =
+                coreV1Api
+                        .listNamespacedPod("group25", null, null, null, null, null, null, null, null, null)
+                        .getItems()
+                        .get(0);
+        PodLogs logs = new PodLogs();
+        InputStream inputStream = logs.streamNamespacedPodLog(pod);
+
+        byte[] data = ByteStreams.toByteArray(inputStream);
+        System.out.print(data.length);
+//        ByteStreams.copy(inputStream, System.out);
+    }
+}

+ 195 - 19
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/NamespaceApiImpl.java

@@ -2,20 +2,29 @@ package nju.seec.SEECdemo.logic.api.k8s.impl;
 
 import io.kubernetes.client.ApiException;
 import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1DeleteOptions;
+import io.kubernetes.client.models.V1DeleteOptionsBuilder;
 import io.kubernetes.client.models.V1Namespace;
+import io.kubernetes.client.models.V1NamespaceList;
 import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.model.LabelSelector;
+import nju.seec.SEECdemo.logic.api.k8s.model.Namespace;
 import nju.seec.SEECdemo.util.LoggerUtil;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
 
-/**
- * author: rale
- * createdAt: 12/22/18
- */
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.*;
+import static nju.seec.SEECdemo.util.Constants.FOREGROUND_PROPAGATION_POLICY;
+import static nju.seec.SEECdemo.util.Constants.METADATA_NAME_SELECTOR;
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
 @Service
 public class NamespaceApiImpl implements NamespaceApi{
 
@@ -25,26 +34,193 @@ public class NamespaceApiImpl implements NamespaceApi{
     private CoreV1Api coreV1Api;
 
     /**
-     * @todo 此处没有明确项目的LimitRange
      * @param namespace
      * @return
      * @see NamespaceApi#create(Namespace)
      */
     @Override
-    public ApiResult<Void> create(Namespace namespace) {
+    public Namespace create(Namespace namespace) {
         V1Namespace v1Namespace = namespace.toV1Namespace();
         try {
-            coreV1Api.createNamespace(v1Namespace, "true");
+            V1Namespace result = coreV1Api.createNamespace(v1Namespace, PRETTY_FORMAT);
+            return result == null ? null : new Namespace(result);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Namespace创建失败,namespace={}, v1Namespace={}, response={}", namespace, v1Namespace, e.getResponseBody());
+            if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(K8sApiException.NAMESPACE_ALREADY_EXIST);
+            }else{
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "Namespace创建异常, namespace={}, v1Namespace={}", namespace, v1Namespace);
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @param name
+     * @throws K8sApiException
+     */
+    @Override
+    public void delete(String name) {
+        try {
+            deleteNamespace(name);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Namespace删除失败,name={}, response={}", name , e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw K8s_NAMESPACE_NOT_EXIST;
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            //k8s 由swagger导致的bug
+            LoggerUtil.error(logger, e, "Namespace删除异常, name={}", name);
+        }
+    }
+
+    @Override
+    public void deleteIfExists(String name) {
+        try {
+            deleteNamespace(name);
+        } catch (ApiException e) {
+            //只打印系统执行一场
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "Namespace删除失败,name={}, response={}", name , e.getResponseBody());
+            }
+        } catch (Exception e) {
+            //k8s 由swagger导致的bug
+            LoggerUtil.error(logger, e, "Namespace删除异常, name={}", name);
+        }
+    }
+
+    @Override
+    public Namespace getByName(String name) {
+        try {
+            V1Namespace v1Namespace = coreV1Api.readNamespace(
+                    name,
+                    PRETTY_FORMAT,
+                    null,
+                    null);
+            return v1Namespace == null ? null : new Namespace(v1Namespace);
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查询Namespace失败,name={}, response={}", name, e.getResponseBody());
+            if (e.getCode() != NOT_FOUND) {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<Namespace> getAll() {
+        try {
+            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    true,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+            List<Namespace> namespaces =  v1NamespaceList.getItems().stream().map(Namespace::new).collect(Collectors.toList());
+            LoggerUtil.info(logger,"result={}", namespaces);
+            return namespaces;
         } catch (ApiException e) {
-            LoggerUtil.error(logger, e, "Namespace创建失败,namespace={}, v1Namespace={}", namespace, v1Namespace);
-            ApiResult<Void> result = new ApiResult<>();
-            result.setSuccess(false);
-            result.setErrorCode(e.getCode());
-            return result;
-        }
-        ApiResult<Void> result = new ApiResult<>();
-        result.setSuccess(true);
-        return result;
+            LoggerUtil.error(logger, e, "获取全部Namespace异常, response={}", e.getResponseBody());
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public List<Namespace> getByLabels(Map<String, String> labels) {
+        try {
+            String labelSelector = LabelSelector.toJsonString(labels);
+            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    true,
+                    labelSelector,
+                    null,
+                    null,
+                    null,
+                    null);
+            List<Namespace> namespaces =  v1NamespaceList.getItems().stream().map(Namespace::new).collect(Collectors.toList());
+            LoggerUtil.info(logger,"result={}", namespaces);
+            return namespaces;
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "根据标签获取Namespace异常, labels={}, response={}", labels, e.getResponseBody());
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public List<Namespace> getByLabel(String key, String value){
+        return getByLabels(Collections.singletonMap(key, value));
+    }
+
+    @Override
+    public List<Namespace> getByLabelSelector(LabelSelector labelSelector) {
+        try {
+            String selector = labelSelector.toJsonString();
+            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    true,
+                    selector,
+                    null,
+                    null,
+                    null,
+                    null);
+            List<Namespace> namespaces =  v1NamespaceList.getItems().stream().map(Namespace::new).collect(Collectors.toList());
+            LoggerUtil.info(logger,"result={}", namespaces);
+            return namespaces;
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "根据标签获取Namespace异常, labelSelector={}, response={}", labelSelector, e.getResponseBody());
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+        }
+    }
+
+    @Override
+    public boolean exists(String name) {
+        try {
+            V1Namespace v1Namespace = coreV1Api.readNamespace(
+                    name,
+                    PRETTY_FORMAT,
+                    null,
+                    null);
+            return v1Namespace != null;
+        }catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "查询命名空间失败, name={}, response={}", name, e.getResponseBody());
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * 删除命名空间,执行错误会抛出一切异常
+     * @param name
+     * @throws ApiException
+     */
+    private void deleteNamespace(String name) throws ApiException{
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Namespace.VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        coreV1Api.deleteNamespace(
+                name,
+                v1DeleteOptions,
+                PRETTY_FORMAT,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY);
     }
 
 }

+ 169 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ResourceQuotaImpl.java

@@ -0,0 +1,169 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import io.kubernetes.client.models.V1ResourceQuota;
+import nju.seec.SEECdemo.logic.api.k8s.ResourceQuotaApi;
+
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1DeleteOptions;
+import io.kubernetes.client.models.V1DeleteOptionsBuilder;
+import io.kubernetes.client.models.V1ResourceQuotaList;
+import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
+import nju.seec.SEECdemo.logic.api.k8s.model.ResourceQuota;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.NOT_FOUND;
+import static nju.seec.SEECdemo.util.Constants.FOREGROUND_PROPAGATION_POLICY;
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
+@Service
+public class ResourceQuotaImpl implements ResourceQuotaApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(ResourceQuotaApi.class);
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    private NamespaceApi namespaceApi;
+
+    @Override
+    public ResourceQuota create(ResourceQuota resourceQuota) throws K8sApiException{
+        try {
+            V1ResourceQuota v1ResourceQuota = resourceQuota.toV1ResourceQuota();
+            V1ResourceQuota result = coreV1Api.createNamespacedResourceQuota(
+                    resourceQuota.getNamespace(),
+                    v1ResourceQuota,
+                    PRETTY_FORMAT);
+            return result == null ? null : new ResourceQuota(v1ResourceQuota);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "创建resource quota失败, resourceQuota={}, response={}",
+                    resourceQuota, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "命名空间不存在");
+            }else if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "资源配额已经存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "创建resource quota失败, resourceQuota={}", resourceQuota);
+        }
+        return null;
+    }
+
+    @Override
+    public void delete(String namespace, String name) throws K8sApiException{
+        try {
+
+            deleteResourceQuota(namespace, name);
+
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "删除resource quota失败,namespace={}, name={}, response={}",
+                    namespace, name, e.getResponseBody());
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(NOT_FOUND, "资源配额不存在");
+            }else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public void deleteIfExists(String namespace, String name) {
+        try {
+
+            deleteResourceQuota(namespace, name);
+
+        } catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "删除resource quota失败,namespace={}, name={}, response={}",
+                        namespace, name, e.getResponseBody());
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "删除resource quota异常,namespace={}, name={}", namespace, name);
+        }
+    }
+
+    @Override
+    public ResourceQuota getByName(String namespace, String name) throws K8sApiException {
+        try {
+
+            V1ResourceQuotaList resourceQuotaList = coreV1Api.listNamespacedResourceQuota(namespace,
+                    PRETTY_FORMAT,
+                    null,
+                    "metadata.name="+name,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+            return new ResourceQuota(resourceQuotaList.getItems().get(0));
+        } catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "查找resource quota失败, namespace={}, name={}, response={}",
+                        namespace,
+                        name,
+                        e.getResponseBody());
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean exist(String namespace, String name) throws K8sApiException {
+        try {
+
+            V1ResourceQuotaList resourceQuotaList = coreV1Api.listNamespacedResourceQuota(namespace,
+                    PRETTY_FORMAT,
+                    null,
+                    "metadata.name="+name,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+            return resourceQuotaList.getItems().size() > 0;
+        } catch (ApiException e) {
+            if (e.getCode() != NOT_FOUND) {
+                LoggerUtil.error(logger, e, "查找resource quota失败, namespace={}, name={}, response={}",
+                        namespace,
+                        name,
+                        e.getResponseBody());
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "查找resource quota异常, namespace={}, name={}",
+                    namespace,
+                    name);
+        }
+        return false;
+    }
+
+    private void deleteResourceQuota(String namespace, String name) throws ApiException{
+        V1DeleteOptions deleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(ResourceQuota.API_VERSION)
+                .withApiVersion(ResourceQuota.KIND)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        coreV1Api.deleteNamespacedResourceQuota(
+                name,
+                namespace,
+                deleteOptions,
+                PRETTY_FORMAT,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY);
+    }
+}

+ 146 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/SecretApiImpl.java

@@ -0,0 +1,146 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.*;
+import nju.seec.SEECdemo.logic.api.k8s.SecretApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.util.SecretTypeEnum;
+import nju.seec.SEECdemo.logic.api.k8s.vo.SecretVO;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
+
+@Service
+public class SecretApiImpl implements SecretApi {
+
+    private static final Logger logger = LoggerUtil.getLogger(SecretApi.class);
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Override
+    public SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data) throws K8sApiException {
+        return createGenericSecret(namespace, name, data, SecretTypeEnum.Generic);
+    }
+
+    @Override
+    public SecretVO createGenericSecret(String namespace, String name, Map<String, byte[]> data, SecretTypeEnum type) throws K8sApiException {
+
+        //Secret默认创建的Type为Opaque,base64编码格式的Secret,用来储存密码、密钥等。
+
+        V1Secret v1Secret = new V1Secret();
+        V1ObjectMeta meta = new V1ObjectMeta();
+        meta.setName(name);
+        v1Secret.setApiVersion("v1");
+        meta.setNamespace(namespace);
+        v1Secret.setData(data);
+        v1Secret.setKind("Secret");
+        v1Secret.setMetadata(meta);
+        v1Secret.setType(type.getValue());
+
+        try {
+            return new SecretVO(coreV1Api.createNamespacedSecret(namespace, v1Secret, PRETTY_FORMAT));
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Secret创建失败, Secret={}, response={}", v1Secret, e.getResponseBody());
+            switch (e.getCode()) {
+                case K8sApiException.NAMESPACE_NOT_EXIST:
+                    throw new K8sApiException(K8sApiException.NAMESPACE_NOT_EXIST);
+                case K8sApiException.ALREADY_EXIST:
+                    throw new K8sApiException(K8sApiException.ALREADY_EXIST);
+                default:
+                    throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public SecretVO createPrivateRegistrySecret(String namespace, String server, String user, String password) throws K8sApiException {
+        //dockerSecret 统一的名称
+        String name = "docker-registry-secret";
+        return createPrivateRegistrySecret(namespace, name, server, user, password);
+
+    }
+
+    @Override
+    public SecretVO createPrivateRegistrySecret(String namespace, String name, String server, String user, String password) throws K8sApiException {
+        Map<String, byte[]> data = new HashMap<>();
+
+        String authInfo = user + ":" + password;
+        String auth = Base64.getEncoder().encodeToString(authInfo.getBytes());
+        String dockerConfigJson =
+                "{\"auths\":{\"" + server + "\":{\"username\":\"" + user +
+                        "\",\"password\":\"" + password +
+                        "\",\"auth\":\"" + auth + "\"}}}";
+        data.put(".dockerconfigjson", dockerConfigJson.getBytes());
+
+        return createGenericSecret(namespace, name, data, SecretTypeEnum.REGISTRY);
+    }
+
+    @Override
+    public List<SecretVO> getSecretList(String namespace) throws K8sApiException {
+        try {
+            V1SecretList v1SecretList = coreV1Api.listNamespacedSecret(
+                    namespace,
+                    PRETTY_FORMAT,
+                    "",
+                    "",
+                    false,
+                    "",
+                    200,
+                    "",
+                    10,
+                    false);
+            return v1SecretList.getItems().stream().map(SecretVO::new).collect(Collectors.toList());
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Secret列表获取失败, response={}", e.getResponseBody());
+            if (e.getCode() == K8sApiException.NAMESPACE_NOT_EXIST) {
+                throw new K8sApiException(K8sApiException.NAMESPACE_NOT_EXIST);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public List<SecretVO> getSecretListByType(String namespace, SecretTypeEnum type) throws K8sApiException {
+        return getSecretList(namespace)
+                .stream()
+                .filter(secretVO -> type == secretVO.getSecretType())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public SecretVO getSecretByName(String namespace, String name) throws K8sApiException {
+        return getSecretList(namespace)
+                .stream()
+                .filter(secretVO -> StringUtils.equals(secretVO.getName(), name))
+                .findAny()
+                .get();
+    }
+
+    @Override
+    public void deleteSecretByName(String namespace, String name) throws K8sApiException {
+        V1DeleteOptions body = new V1DeleteOptions();
+        try {
+            //暂时没有用到deleteNamespacedSecret()中的其他参数。
+            V1Status v1Status = coreV1Api.deleteNamespacedSecret(name, namespace, body, PRETTY_FORMAT, 5, false, "");
+        } catch (ApiException e) {
+            if (e.getCode() == K8sApiException.NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND);
+            } else {
+                throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+}
+

+ 101 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ServiceApiImpl.java

@@ -0,0 +1,101 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1DeleteOptions;
+import io.kubernetes.client.models.V1DeleteOptionsBuilder;
+import io.kubernetes.client.models.V1Service;
+import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
+import nju.seec.SEECdemo.logic.api.k8s.ServiceApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress;
+import nju.seec.SEECdemo.logic.api.k8s.model.Service;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.ALREADY_EXIST;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
+import static nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException.NOT_FOUND;
+import static nju.seec.SEECdemo.util.Constants.FOREGROUND_PROPAGATION_POLICY;
+import static nju.seec.SEECdemo.util.Constants.PRETTY_FORMAT;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+@org.springframework.stereotype.Service
+public class ServiceApiImpl implements ServiceApi{
+    private static final Logger logger = LoggerUtil.getLogger(ServiceApi.class);
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Override
+    public void create(Service service) {
+        try {
+            V1Service v1Service = service.toV1Service();
+            coreV1Api.createNamespacedService(service.getNamespace(), v1Service, PRETTY_FORMAT);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "service创建失败, service={}, response={}", service, e.getResponseBody());
+            if (e.getCode() == ALREADY_EXIST) {
+                throw new K8sApiException(ALREADY_EXIST, "Service已经存在");
+            }else if (e.getCode() == NOT_FOUND){
+                throw new K8sApiException(NOT_FOUND, "Namespace不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }
+    }
+
+    @Override
+    public void delete(String namespace, String name) {
+        try {
+
+            deleteService(namespace, name);
+
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Service删除失败,namespace={}, name={}, response={}",
+                    namespace, name, e.getResponseBody());
+
+            if (e.getCode() == NOT_FOUND) {
+                throw new K8sApiException(K8sApiException.NOT_FOUND, "Service不存在");
+            }else {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        }catch (Exception e) {
+            LoggerUtil.error(logger, e, "Service删除异常,namespace={}, name={}, response={}", namespace, name);
+        }
+    }
+
+    @Override
+    public void deleteIfExists(String namespace, String name) {
+        try {
+            deleteService(namespace, name);
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "Service删除失败,namespace={}, name={}, response={}",
+                    namespace, name, e.getResponseBody());
+
+            if (e.getCode() != NOT_FOUND) {
+                throw K8s_SYSTEM_ERROR_EXCEPTION;
+            }
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "Service删除异常,namespace={}, name={}, response={}", namespace, name);
+        }
+    }
+
+    private void deleteService(String namespace, String name) throws ApiException{
+        V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
+                .withApiVersion(Service.API_VERSION)
+                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+                .build();
+        coreV1Api.deleteNamespacedService(
+                name,
+                namespace,
+                v1DeleteOptions,
+                PRETTY_FORMAT,
+                null,
+                null,
+                FOREGROUND_PROPAGATION_POLICY);
+    }
+}

+ 10 - 8
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Container.java → src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Container.java

@@ -1,4 +1,4 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
+package nju.seec.SEECdemo.logic.api.k8s.model;
 
 import io.kubernetes.client.models.V1Container;
 import io.kubernetes.client.models.V1ContainerBuilder;
@@ -13,16 +13,19 @@ import javax.validation.constraints.Pattern;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static nju.seec.SEECdemo.logic.api.k8s.vo.Constants.NAME_PATTERN;
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
 
 /**
  * author: rale
- * createdAt: 12/23/18
+ * createdAt: 1/3/19
  */
 @Data
 @NoArgsConstructor
 public class Container {
 
+    private static final String IMAGE_PULL_POLICY = "Always";
+//    private static final String IMAGE_PULL_POLICY = "IfNotPresent";
+
     @NotNull
     @Pattern(regexp = NAME_PATTERN)
     private String name;
@@ -31,7 +34,7 @@ public class Container {
     private String image;
 
 
-    private Set<ContainerPort>  ports = new HashSet<>();
+    private Set<ContainerPort> ports = new HashSet<>();
 
     /* 启动指令的参数 */
     private List<String> args;
@@ -42,6 +45,7 @@ public class Container {
     /* 环境变量 */
     private Map<String, String> env = new HashMap<>();
 
+
     public Container(V1Container v1Container) {
         this.name = v1Container.getName();
         this.image = v1Container.getImage();
@@ -50,12 +54,11 @@ public class Container {
         this.command = v1Container.getCommand();
         this.env = v1Container.getEnv().stream().collect(Collectors.toMap(V1EnvVar::getName, V1EnvVar::getValue));
     }
-
     public V1Container toV1Container(){
-        V1Container container = new V1ContainerBuilder()
+        return new V1ContainerBuilder()
                 .withName(name)
                 .withImage(image)
-                .withImagePullPolicy("Always")
+                .withImagePullPolicy(IMAGE_PULL_POLICY)
                 .withArgs(args)
                 .withCommand(command)
                 .withPorts(ports.stream().map(ContainerPort::toV1ContainerPort).collect(Collectors.toList()))
@@ -70,6 +73,5 @@ public class Container {
                         .collect(Collectors.toList())
                 )
                 .build();
-        return container;
     }
 }

+ 5 - 6
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ContainerPort.java → src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ContainerPort.java

@@ -1,4 +1,4 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
+package nju.seec.SEECdemo.logic.api.k8s.model;
 
 import io.kubernetes.client.models.V1ContainerPort;
 import io.kubernetes.client.models.V1ContainerPortBuilder;
@@ -10,7 +10,7 @@ import org.hibernate.validator.constraints.Range;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Pattern;
 
-import static nju.seec.SEECdemo.logic.api.k8s.vo.Constants.NAME_PATTERN;
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
 
 /**
  * author: rale
@@ -34,10 +34,9 @@ public class ContainerPort {
     private String protocol;
 
     public ContainerPort(V1ContainerPort v1ContainerPort) {
-        ContainerPort containerPort = new ContainerPort();
-        containerPort.setName(v1ContainerPort.getName());
-        containerPort.setPort(v1ContainerPort.getContainerPort());
-        containerPort.setProtocol(v1ContainerPort.getProtocol());
+        this.setName(v1ContainerPort.getName());
+        this.setPort(v1ContainerPort.getContainerPort());
+        this.setProtocol(v1ContainerPort.getProtocol());
     }
     public V1ContainerPort toV1ContainerPort(){
         V1ContainerPort v1ContainerPort = new V1ContainerPortBuilder()

+ 59 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/CpuQuantity.java

@@ -0,0 +1,59 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.custom.Quantity;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * CPU配额数量,默认单位为m,即CPU时间
+ */
+@Data
+@NoArgsConstructor
+public class CpuQuantity {
+
+    private Quantity quantity;
+
+    private Unit unit = Unit.Mi;
+
+    //Cpu=0
+    public static final CpuQuantity EMPTY = new CpuQuantity(0);
+    //Cpu的默认分配额度
+    public static final CpuQuantity DEFAULT = new CpuQuantity(50);
+
+    //Cpu单位
+    public enum Unit{
+        Mi("m");
+
+        private String code;
+
+        Unit(String code) {
+            this.code = code;
+        }
+
+        public String getCode(){
+            return this.code;
+        }
+    }
+
+
+    public CpuQuantity(int quantity){
+        this.quantity = Quantity.fromString(quantity + unit.getCode());
+    }
+
+    public CpuQuantity(long quantity) {
+        this.quantity = Quantity.fromString(quantity + unit.getCode());
+    }
+
+
+    public CpuQuantity(Quantity quantity) {
+        this.quantity = quantity;
+    }
+
+    public Quantity toQuantity(){
+        return quantity;
+    }
+
+    public String getQuantity() {
+        return quantity.toSuffixedString();
+    }
+}

+ 24 - 38
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Deployment.java → src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Deployment.java

@@ -1,4 +1,4 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
+package nju.seec.SEECdemo.logic.api.k8s.model;
 
 import io.kubernetes.client.models.*;
 import lombok.Data;
@@ -6,62 +6,52 @@ import lombok.NoArgsConstructor;
 import org.hibernate.validator.constraints.Range;
 
 import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static nju.seec.SEECdemo.logic.api.k8s.vo.Constants.APPLICATION_LABEL;
-
 /**
  * author: rale
- * createdAt: 12/23/18
+ * createdAt: 1/3/19
  */
 @Data
 @NoArgsConstructor
 public class Deployment {
 
-    private static final String DEPLOYMENT_VERSION = "apps/v1";
+    public static final String API_VERSION = "apps/v1";
 
     private static final String DEPLOYMENT_KIND = "Deployment";
 
-    private static final String SERVICE_VERSION = "v1";
-
-    private static final String SERVICE_KIND = "Service";
-
     @NotEmpty
     private String name;
 
-    @NotNull
     private String namespace;
 
-    //@todo 加上部署应用的类型,如数据库,web应用
+    private List<Container> containers = new ArrayList<>();
+
+    private List<String> imagePullSecrets = new ArrayList<>();
 
     @Range(min = 1, max = 3)
     private int replicas;
 
-    private List<Container> containers = new ArrayList<>();
+    private Map<String, String> labels = new HashMap<>();
 
-    private Set<ServicePort> servicePorts = new HashSet<>();
+    private Map<String, String> annotations = new HashMap<>();
 
+    public Deployment(V1Deployment v1Deployment) {
+        this.name = v1Deployment.getMetadata().getName();
+        this.namespace = v1Deployment.getMetadata().getNamespace();
+        this.containers = v1Deployment.getSpec().getTemplate().getSpec().getContainers()
+                .stream().map(Container::new).collect(Collectors.toList());
+    }
     public V1Deployment toV1Deployment(){
-        V1Deployment v1Deployment = new V1DeploymentBuilder()
-                .withApiVersion(DEPLOYMENT_VERSION)
+        return new V1DeploymentBuilder()
+                .withApiVersion(API_VERSION)
                 .withKind(DEPLOYMENT_KIND)
                 .withMetadata(this.toV1ObjectMeta())
                 .withSpec(this.toV1DeploymentSpec())
                 .build();
-        return v1Deployment;
     }
 
-    public V1Service toV1Service() {
-        V1Service v1Service = new V1ServiceBuilder()
-                .withApiVersion(SERVICE_VERSION)
-                .withKind(SERVICE_KIND)
-                .withMetadata(this.toV1ObjectMeta())
-                .withSpec(this.toV1ServiceSpec())
-                .build();
-        return v1Service;
-    }
     private V1ObjectMeta toV1ObjectMeta(){
         return new V1ObjectMetaBuilder()
                 .withName(name)
@@ -71,17 +61,22 @@ public class Deployment {
 
     private V1LabelSelector toDeploymentV1LabelSelector() {
         return new V1LabelSelectorBuilder()
-                .withMatchLabels(Collections.singletonMap(APPLICATION_LABEL, name))
+                .withMatchLabels(labels)
                 .build();
     }
 
     private V1PodTemplateSpec toPodSpecTemplate(){
         V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
-                .withLabels(Collections.singletonMap(APPLICATION_LABEL, name))
+                .withLabels(labels)
+                .withAnnotations(annotations)
                 .build();
+        List<V1LocalObjectReference> references = imagePullSecrets
+                .stream()
+                .map(secret -> new V1LocalObjectReference().name(secret))
+                .collect(Collectors.toList());
         V1PodSpec v1PodSpec = new V1PodSpecBuilder()
                 .addAllToContainers(containers.stream().map(Container::toV1Container).collect(Collectors.toList()))
-                .withImagePullSecrets(new V1LocalObjectReference().name("regcred"))
+                .withImagePullSecrets(references)
                 .build();
         V1PodTemplateSpec v1PodTemplateSpec = new V1PodTemplateSpecBuilder()
                 .withMetadata(v1ObjectMeta)
@@ -97,13 +92,4 @@ public class Deployment {
                 .withTemplate(this.toPodSpecTemplate())
                 .build();
     }
-
-    private V1ServiceSpec toV1ServiceSpec(){
-        V1ServiceSpec serviceSpec = new V1ServiceSpecBuilder()
-                .withSelector(Collections.singletonMap(APPLICATION_LABEL, name))
-                .withPorts(servicePorts.stream().map(ServicePort::toV1ServicePort).collect(Collectors.toList()))
-                .build();
-        return serviceSpec;
-    }
-
 }

+ 118 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Ingress.java

@@ -0,0 +1,118 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.models.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * author: rale
+ * createdAt: 1/5/19
+ */
+@Data
+@NoArgsConstructor
+public class Ingress {
+    public static final String API_VERSION = "extensions/v1beta1";
+
+    private static final String KIND = "Ingress";
+
+    private static final String REWRITE_ANNOTATION = "nginx.ingress.kubernetes.io/rewrite-target";
+
+    private static final String DEFAULT_REWRITE_PATH = "/";
+    private String namespace;
+
+    private String name;
+
+    //重定向的路径,默认为根路径
+    private String redirectPath = DEFAULT_REWRITE_PATH;
+
+    private String httpHost;
+
+    /*Ingress能暴露HTTP和HTTPS协议口,目前暂不支持HTTPS协议*/
+    private Set<IngressDetail> httpRules = new HashSet<>();
+
+    public Ingress(V1beta1Ingress v1beta1Ingress) {
+        this.name = v1beta1Ingress.getMetadata().getName();
+        this.namespace = v1beta1Ingress.getMetadata().getNamespace();
+        this.redirectPath = v1beta1Ingress.getMetadata().getAnnotations().getOrDefault(REWRITE_ANNOTATION, DEFAULT_REWRITE_PATH);
+        List<V1beta1IngressRule> rules = v1beta1Ingress.getSpec().getRules();
+        if (rules != null && !rules.isEmpty()) {
+            for (V1beta1IngressRule rule : rules) {
+                httpHost = rule.getHost();
+                if (rule.getHttp() != null && rule.getHttp().getPaths() != null) {
+                    httpRules = rule.getHttp().getPaths().stream().map(IngressDetail::new).collect(Collectors.toSet());
+                }
+            }
+        }
+    }
+
+    @Data
+    @NoArgsConstructor
+    @EqualsAndHashCode
+    public static class IngressDetail {
+
+        private String path;
+
+        private String serviceName;
+
+        private int port;
+
+        public IngressDetail(V1beta1HTTPIngressPath path) {
+            this.path = path.getPath();
+            this.serviceName = path.getBackend().getServiceName();
+            this.port = path.getBackend().getServicePort().getIntValue();
+        }
+    }
+
+
+    public V1beta1Ingress toV1beta1Ingress(){
+        V1beta1IngressSpec v1beta1IngressSpec = new V1beta1IngressSpecBuilder()
+                .withRules(toV1beta1IngressRule())
+                .build();
+        V1beta1Ingress v1beta1Ingress = new V1beta1IngressBuilder()
+                .withApiVersion(API_VERSION)
+                .withKind(KIND)
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(v1beta1IngressSpec)
+                .build();
+        return v1beta1Ingress;
+    }
+
+    private V1ObjectMeta toV1ObjectMeta() {
+        return new V1ObjectMetaBuilder()
+                .withName(name)
+                .withNamespace(namespace)
+                .addToAnnotations(REWRITE_ANNOTATION, redirectPath)
+                .build();
+    }
+
+    private V1beta1HTTPIngressRuleValue toV1beta1HTTPIngressRuleValue() {
+        List<V1beta1HTTPIngressPath> paths = httpRules.stream().map(rule -> {
+                    V1beta1HTTPIngressPath v1beta1HTTPIngressPath = new V1beta1HTTPIngressPath();
+                    v1beta1HTTPIngressPath.setPath(rule.getPath());
+                    V1beta1IngressBackend v1beta1IngressBackend = new V1beta1IngressBackend();
+                    v1beta1IngressBackend.setServiceName(rule.getServiceName());
+                    v1beta1IngressBackend.setServicePort(new IntOrString(rule.getPort()));
+
+                    v1beta1HTTPIngressPath.setBackend(v1beta1IngressBackend);
+                    return v1beta1HTTPIngressPath;
+        }).collect(Collectors.toList());
+
+        return new V1beta1HTTPIngressRuleValueBuilder()
+                .withPaths(paths)
+                .build();
+    }
+
+    private V1beta1IngressRule toV1beta1IngressRule() {
+        return new V1beta1IngressRuleBuilder()
+                .withHost(httpHost)
+                .withHttp(toV1beta1HTTPIngressRuleValue())
+                .build();
+    }
+}

+ 153 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/LabelSelector.java

@@ -0,0 +1,153 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+
+/**
+ * author: rale
+ * createdAt: 1/7/19
+ *
+ * 标签选择器
+ * 支持以下几种语法
+ * key IN value1,value2 ... 筛选出键为key且值在value中的资源 key equals value 等价于key in value
+ * key NOT_IN value1, value2 ... 筛选出键为key且值不在value中,或者不存在键为key的键值对的资源 key not equals value 等价于 key not in value
+ * Key EXISTS 筛选出包含键key的资源
+ * Key NOT_EXISTS 筛选出不包含键key的资源
+ *
+ * 本选择器不会删除同名的key
+ */
+@NoArgsConstructor
+public class LabelSelector {
+
+    public enum Operator{
+
+        IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " in " + resultValue;
+            }
+        },
+
+        NOT_IN {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                Set<String> values = matchExpressionItem.getValues();
+                if (values == null || values.size() == 0) return "";
+                if (values.size() == 1) return matchExpressionItem.getKey() + "!=" + values.iterator().next();
+                StringBuilder resultValue = new StringBuilder("(");
+                for (String value : matchExpressionItem.getValues()) {
+                    if (resultValue.length() > 1) {
+                        resultValue.append(",");
+                    }
+                    resultValue.append(value);
+                }
+                resultValue.append(")");
+                return matchExpressionItem.getKey() + " notin " + resultValue;
+            }
+        },
+
+        EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return matchExpressionItem.getKey();
+            }
+        },
+
+        NOT_EXISTS {
+            @Override
+            public String toJsonString(MatchExpressionItem matchExpressionItem) {
+                return "!" + matchExpressionItem.getKey();
+            }
+        };
+
+        public abstract String toJsonString(MatchExpressionItem matchExpressionItem);
+    }
+
+
+    private List<MatchExpressionItem> matchExpressionItems;
+
+
+    public void addMatchExpression(String key, Operator operator, Set<String> values) {
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, values));
+    }
+
+
+    public void addMatchExpression(String key, Operator operator, String... values) {
+        Set<String> valueSet = new HashSet<>(Arrays.asList(values));
+        addMatchExpressionItem(new MatchExpressionItem(key, operator, valueSet));
+    }
+
+
+    private void addMatchExpressionItem(MatchExpressionItem matchExpressionItem) {
+        if (matchExpressionItem == null) {
+            return;
+        }
+        if (matchExpressionItems == null) {
+            matchExpressionItems = new LinkedList<>();
+        }
+        matchExpressionItems.add(matchExpressionItem);
+    }
+
+    @Data
+    @EqualsAndHashCode
+    @NoArgsConstructor
+    @AllArgsConstructor
+    private static class MatchExpressionItem{
+
+        private String key;
+
+        private Operator operator;
+
+        private Set<String> values;
+
+        private String toJsonString() {
+            return operator == null ? "" : operator.toJsonString(this);
+        }
+
+    }
+
+    public String toJsonString() {
+        StringBuilder sb = new StringBuilder();
+
+        if (matchExpressionItems != null && !matchExpressionItems.isEmpty()) {
+            for (MatchExpressionItem matchExpressionItem : matchExpressionItems) {
+                if (sb.length() > 0) {
+                    sb.append(",");
+                }
+                sb.append(matchExpressionItem.toJsonString());
+            }
+        }
+        return sb.toString();
+    }
+
+    public static String toJsonString(Map<String, String> labels) {
+        StringBuilder result = new StringBuilder();
+        for (Map.Entry<String, String> label : labels.entrySet()) {
+            if (result.length() > 0) {
+                result.append(",");
+            }
+            result.append(toJsonString(label.getKey(), label.getValue()));
+        }
+        return result.toString();
+    }
+
+    public static String toJsonString(String key, String value) {
+        return key + "=" + value;
+    }
+
+}

+ 66 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Namespace.java

@@ -0,0 +1,66 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.models.*;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nju.seec.SEECdemo.util.DateUtil;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * author: rale
+ * createdAt: 1/4/19
+ */
+@Data
+@NoArgsConstructor
+public class Namespace {
+
+    private static final String KIND = "Namespace";
+
+    public static final String VERSION = "v1";
+
+    @NotNull
+    private String name;
+
+    private Map<String, String> labels = new HashMap<>();
+
+    private Map<String, String> annotations = new HashMap<>();
+
+    @ApiModelProperty(readOnly = true)
+    private LocalDateTime createdAt;
+
+
+    public Namespace(V1Namespace v1Namespace) {
+        this.name = v1Namespace.getMetadata().getName();
+        this.labels = v1Namespace.getMetadata().getLabels();
+        this.annotations = v1Namespace.getMetadata().getAnnotations();
+        this.createdAt = DateUtil.fromJodaDateTime(v1Namespace.getMetadata().getCreationTimestamp());
+    }
+
+    public V1Namespace toV1Namespace(){
+        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
+                .withName(name)
+                .withLabels(labels)
+                .withAnnotations(annotations)
+                .build();
+        V1Namespace v1Namespace = new V1NamespaceBuilder()
+                .withApiVersion(VERSION)
+                .withKind(KIND)
+                .withMetadata(v1ObjectMeta)
+                .build();
+        return v1Namespace;
+
+    }
+
+    public void addToLabel(String key, String value) {
+        labels.put(key, value);
+    }
+
+    public void addToAnnotation(String key, String value){
+        annotations.put(key, value);
+    }
+}

+ 84 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ResourceQuota.java

@@ -0,0 +1,84 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.custom.Quantity;
+import io.kubernetes.client.models.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * author: rale
+ * createdAt: 1/4/19
+ */
+@Data
+@NoArgsConstructor
+//@todo 添加注释
+public class ResourceQuota {
+    public static final String API_VERSION = "v1";
+
+    public static final String KIND = "ResourceQuota";
+
+    private static final String HARD_REQUEST_CPU = "requests.cpu";
+    private static final String HARD_REQUEST_MEMORY = "requests.memory";
+    private static final String HARD_LIMITS_CPU = "limits.cpu";
+    private static final String HARD_LIMITS_MEMORY = "limits.memory";
+
+    private String namespace;
+
+    private String name;
+
+    /**
+     * 命名空间下请求CPU资源的最大数额
+     */
+    private CpuQuantity maxCpuTotalRequest;
+
+    private StorageQuantity maxMemoryTotalRequest;
+
+    /**
+     *
+     */
+    private CpuQuantity maxCpuTotalLimit;
+
+    private StorageQuantity maxMemoryTotalLimit;
+
+    public ResourceQuota(V1ResourceQuota v1ResourceQuota) {
+        this.namespace = v1ResourceQuota.getMetadata().getNamespace();
+        this.name = v1ResourceQuota.getMetadata().getName();
+        this.maxCpuTotalLimit = new CpuQuantity(v1ResourceQuota.getSpec().getHard().get(HARD_LIMITS_CPU));
+        this.maxMemoryTotalLimit = new StorageQuantity(v1ResourceQuota.getSpec().getHard().get(HARD_LIMITS_MEMORY));
+    }
+
+    public V1ResourceQuota toV1ResourceQuota(){
+        V1ResourceQuota v1ResourceQuota = new V1ResourceQuotaBuilder()
+                .withApiVersion(API_VERSION)
+                .withKind(KIND)
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(this.toV1ResourceQuotaSpec())
+                .build();
+        return v1ResourceQuota;
+    }
+
+    private V1ResourceQuotaSpec toV1ResourceQuotaSpec(){
+        Map<String, Quantity> map = new HashMap<>();
+        map.put(HARD_REQUEST_CPU, maxCpuTotalRequest.toQuantity());
+        map.put(HARD_LIMITS_CPU, maxCpuTotalLimit.toQuantity());
+        map.put(HARD_REQUEST_MEMORY, maxMemoryTotalLimit.toQuantity());
+        map.put(HARD_LIMITS_MEMORY, maxMemoryTotalLimit.toQuantity());
+
+        V1ResourceQuotaSpec v1ResourceQuotaSpec = new V1ResourceQuotaSpecBuilder()
+                .withHard(map)
+                .build();
+        return v1ResourceQuotaSpec;
+    }
+
+    private V1ObjectMeta toV1ObjectMeta(){
+        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
+                .withNamespace(namespace)
+                .withName(name)
+                .build();
+        return v1ObjectMeta;
+    }
+
+}

+ 50 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/Service.java

@@ -0,0 +1,50 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.models.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Data
+@NoArgsConstructor
+public class Service {
+
+    public static final String API_VERSION = "v1";
+
+    private static final String SERVICE_KIND = "Service";
+
+    private String name;
+
+    private String namespace;
+
+    private Set<ServicePort> servicePorts = new HashSet<>();
+
+    private Map<String, String> selectors = new HashMap<>();
+
+    public V1Service toV1Service() {
+        V1Service v1Service = new V1ServiceBuilder()
+                .withApiVersion(API_VERSION)
+                .withKind(SERVICE_KIND)
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(this.toV1ServiceSpec())
+                .build();
+        return v1Service;
+    }
+
+    private V1ObjectMeta toV1ObjectMeta(){
+        return new V1ObjectMetaBuilder()
+                .withName(name)
+                .withNamespace(namespace)
+                .build();
+    }
+
+    private V1ServiceSpec toV1ServiceSpec() {
+        V1ServiceSpec serviceSpec = new V1ServiceSpecBuilder()
+                .withSelector(selectors)
+                .withPorts(servicePorts.stream().map(ServicePort::toV1ServicePort).collect(Collectors.toList()))
+                .build();
+        return serviceSpec;
+    }
+}

+ 11 - 7
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ServicePort.java → src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/ServicePort.java

@@ -1,4 +1,4 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
+package nju.seec.SEECdemo.logic.api.k8s.model;
 
 import io.kubernetes.client.custom.IntOrString;
 import io.kubernetes.client.models.V1ServicePort;
@@ -22,26 +22,30 @@ public class ServicePort {
     @NotNull
     private String name;
 
+    /**
+     * 服务端口号
+     */
     @Range(min = 1, max = 633)
     private int port;
 
+    /**
+     * 容器端口号
+     */
     @NotNull
-    private IntOrString targetPort;
+    private int targetPort;
 
     public ServicePort(V1ServicePort v1ServicePort) {
         this.name = v1ServicePort.getName();
         this.port = v1ServicePort.getPort();
-        this.targetPort = v1ServicePort.getTargetPort();
+        this.targetPort = v1ServicePort.getTargetPort().getIntValue();
     }
 
     public V1ServicePort toV1ServicePort(){
-        V1ServicePort v1ServicePort = new V1ServicePortBuilder()
+        return new V1ServicePortBuilder()
                 .withName(name)
                 .withPort(port)
-                .withTargetPort(targetPort)
+                .withTargetPort(new IntOrString(targetPort))
                 .build();
-        return v1ServicePort;
     }
 
-
 }

+ 64 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/model/StorageQuantity.java

@@ -0,0 +1,64 @@
+package nju.seec.SEECdemo.logic.api.k8s.model;
+
+import io.kubernetes.client.custom.Quantity;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 存储配额的单位,默认为MB
+ * 可选的为KB,MB,GB
+ */
+@Data
+@NoArgsConstructor
+public class StorageQuantity {
+
+    private Quantity quantity;
+
+    //存储的默认单位为MB
+    private Unit unit = Unit.MB;
+
+    //存储空间为0
+    public static final StorageQuantity EMPTY = new StorageQuantity(0);
+
+    //默认存储空间为500Mi
+    public static final StorageQuantity DEFAULT = new StorageQuantity(500);
+
+    //存储单位
+    public enum Unit{
+        KB("Ki"),
+        MB("Mi"),
+        GB("Gi");
+
+        private String code;
+
+        Unit(String code) {
+            this.code = code;
+        }
+
+        public String getCode() {
+            return code;
+        }
+    }
+
+    public StorageQuantity(int quantity){
+        this.quantity = Quantity.fromString(quantity + unit.getCode());
+    }
+
+    public StorageQuantity(int quantity, Unit unit) {
+        this(quantity);
+        this.unit = unit;
+    }
+
+    public StorageQuantity(Quantity quantity) {
+        this.quantity = quantity;
+    }
+
+    public Quantity toQuantity(){
+        return quantity;
+    }
+
+    public String getQuantity() {
+        return quantity.toSuffixedString();
+    }
+
+}

+ 9 - 23
src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/BeanAnnouncement.java

@@ -2,12 +2,11 @@ package nju.seec.SEECdemo.logic.api.k8s.util;
 
 import io.kubernetes.client.ApiClient;
 import io.kubernetes.client.Configuration;
+import io.kubernetes.client.Exec;
 import io.kubernetes.client.apis.AppsV1Api;
 import io.kubernetes.client.apis.CoreV1Api;
 import io.kubernetes.client.apis.ExtensionsV1beta1Api;
 import io.kubernetes.client.util.Config;
-import nju.seec.SEECdemo.logic.vo.projecttemplate.ProjectTemplate;
-import nju.seec.SEECdemo.data.entity.resource.ResourceConfig;
 import nju.seec.SEECdemo.util.ApplicationProperties;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
@@ -23,6 +22,8 @@ public class BeanAnnouncement {
 
     private final ExtensionsV1beta1Api extensionsV1beta1Api;
 
+    private final Exec exec;
+
     @Autowired
     public BeanAnnouncement(ApplicationProperties applicationProperties) {
         K8s k8s = applicationProperties.getK8s();
@@ -34,10 +35,11 @@ public class BeanAnnouncement {
         this.coreV1Api = new CoreV1Api();
         this.appsV1Api = new AppsV1Api();
         this.extensionsV1beta1Api = new ExtensionsV1beta1Api();
+        this.exec = new Exec();
     }
 
     @Bean
-    public CoreV1Api coreV1Api(){
+    public CoreV1Api coreV1Api() {
         return coreV1Api;
     }
 
@@ -47,29 +49,13 @@ public class BeanAnnouncement {
     }
 
     @Bean
-    public ExtensionsV1beta1Api extensionsV1beta1Api(){
+    public ExtensionsV1beta1Api extensionsV1beta1Api() {
         return extensionsV1beta1Api;
     }
 
-    /**
-     * 软工二的项目模板
-     * @return
-     */
     @Bean
-    public ProjectTemplate seecIITemplate() {
-        ProjectTemplate projectTemplate = new ProjectTemplate();
-        ResourceConfig resourceConfig = new ResourceConfig();
-        resourceConfig.setDefaultCPU(5);
-        resourceConfig.setMaxCPU(10);
-        resourceConfig.setMinCPU(5);
-        resourceConfig.setDefaultMemory(100);
-        resourceConfig.setMaxMemory(500);
-        resourceConfig.setMinMemory(100);
-        resourceConfig.setDefaultStorage(0);
-        resourceConfig.setMaxStorage(1000);
-        projectTemplate.setDefaultResourceConfig(resourceConfig);
-        projectTemplate.setName("软工二大作业");
-        projectTemplate.setDescription("部署单个应用到部署项目下,应用集中连接唯一的数据库");
-        return projectTemplate;
+    public Exec exec() {
+        return exec;
     }
+
 }

+ 41 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/SecretTypeEnum.java

@@ -0,0 +1,41 @@
+package nju.seec.SEECdemo.logic.api.k8s.util;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+public enum SecretTypeEnum {
+    /*基本类型如数据库用户名密码,KEY-VALUE形式*/
+    Generic("Opaque"),
+
+    /*私有仓库的验证信息*/
+    REGISTRY("kubernetes.io/dockerconfigjson"),
+
+    /*账户验证信息,暂未用到*/
+    SERVICE_ACCOUNT("Service Account");
+
+    private String value;
+
+    SecretTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public boolean equals(String value) {
+        return StringUtils.equals(this.value, value);
+    }
+
+    public static SecretTypeEnum getByValue(String value) {
+        for (SecretTypeEnum secretTypeEnum : SecretTypeEnum.values()){
+            if (StringUtils.equals(secretTypeEnum.getValue(), value)) {
+                return secretTypeEnum;
+            }
+        }
+        return null;
+    }
+}

+ 14 - 1
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ApiResult.java

@@ -1,6 +1,7 @@
 package nju.seec.SEECdemo.logic.api.k8s.vo;
 
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.springframework.data.annotation.ReadOnlyProperty;
 
 /**
@@ -9,9 +10,9 @@ import org.springframework.data.annotation.ReadOnlyProperty;
  */
 
 @Data
+@NoArgsConstructor
 public class ApiResult<T> {
 
-    @ReadOnlyProperty
     private boolean success;
 
     private int errorCode;
@@ -23,4 +24,16 @@ public class ApiResult<T> {
     public static final int CONFLICT = 409;//命名冲突
 
     public static final int NOT_EXIST = 800; //资源不存在
+
+    public static final int SYSTEM_ERROR = 100; //系统错误
+    public ApiResult(T t){
+        this.value = t;
+        this.success = true;
+    }
+
+    public ApiResult(int errorCode, String errorDescription) {
+        this.success = false;
+        this.errorCode = errorCode;
+        this.errorDescription = errorDescription;
+    }
 }

+ 0 - 32
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Constants.java

@@ -1,32 +0,0 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
-
-public abstract class Constants {
-
-	//标签域名前缀
-	private static final String LABEL_PREFIX = "demo.seec.nju.cn";
-	//课程前缀
-	static final String TYPE_LABEL = LABEL_PREFIX + "/type";
-
-	static final String DESCRIPTION_LABEL = LABEL_PREFIX + "/description";
-
-	static final String APPLICATION_LABEL = LABEL_PREFIX + "/app";
-	// 用户namespace的label
-	static final String USERSPACE_LABEL = LABEL_PREFIX +"/userspace";
-	// pod的label
-	static final String SELECTOR_LABEL = LABEL_PREFIX + "/app";
-	// 非对外service的label
-	static final String INTERNAL_LABEL = LABEL_PREFIX + "/internal";
-	// 对外service的label
-	static final String EXTERNAL_LABEL = LABEL_PREFIX + "/external";
-	// 持久卷访问模式
-	static final String STORAGE_ACCESS_MODE = "ReadWriteMany";
-	// 持久卷存储类型
-	static final String STORAGE_CLASS = "glusterfs";
-
-	static final String RESOURCE_STORAGE = "storage";
-
-	static final String NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
-
-	static final String NAMESPACE_API_VERSION = "v1";
-
-}

+ 83 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/LimitRange.java

@@ -0,0 +1,83 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.custom.Quantity;
+import io.kubernetes.client.models.*;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nju.seec.SEECdemo.logic.api.k8s.model.CpuQuantity;
+import nju.seec.SEECdemo.logic.api.k8s.model.StorageQuantity;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.util.HashMap;
+import java.util.Map;
+
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
+
+/**
+ * author: mjj
+ * createdAt: 2018/12/30
+ * description:
+ */
+
+@Data
+@NoArgsConstructor
+public class LimitRange {
+
+    @NotNull
+    private String namespace;
+
+    @NotNull
+    @Pattern(regexp = NAME_PATTERN)
+    private String name;
+
+    private Map<String, Integer> _default;
+    private Map<String, Integer> default_request;
+    private Map<String, Integer> max;
+    private Map<String, Integer> min;
+    private String type = "Container";
+
+
+
+    public V1LimitRange toV1LimitRange() {
+        return new V1LimitRangeBuilder()
+                .withApiVersion("v1")
+                .withKind("LimitRange")
+                .withMetadata(this.toV1ObjectMeta())
+                .withSpec(this.toV1LimitRangeSpec())
+                .build();
+    }
+
+    public V1ObjectMeta toV1ObjectMeta() {
+        return new V1ObjectMetaBuilder()
+                .withNamespace(namespace)
+                .withName(name)
+                .build();
+    }
+
+    public V1LimitRangeSpec toV1LimitRangeSpec() {
+        return new V1LimitRangeSpec().addLimitsItem(this.toV1LimitRangeItem());
+    }
+
+    public V1LimitRangeItem toV1LimitRangeItem() {
+        V1LimitRangeItem v1LimitRangeItem = new V1LimitRangeItem();
+        if (_default != null) v1LimitRangeItem.setDefault(exchange(_default));
+        if (default_request != null) v1LimitRangeItem.setDefaultRequest(exchange(default_request));
+        if (max != null) v1LimitRangeItem.setMax(exchange(max));
+        if (min != null) v1LimitRangeItem.setMin(exchange(min));
+        v1LimitRangeItem.setType(type);
+        return v1LimitRangeItem;
+    }
+
+    public Map<String, Quantity> exchange(Map<String, Integer> setting) {
+        Map<String, Quantity> result = new HashMap<>();
+        for (Map.Entry<String, Integer> entry : setting.entrySet()) {
+            if (entry.getKey().equals("cpu")) {
+                result.put(entry.getKey(), new CpuQuantity(entry.getValue()).toQuantity());
+            } else if (entry.getKey().equals("memory")) {
+                result.put(entry.getKey(), new StorageQuantity(entry.getValue()).toQuantity());
+            }
+        }
+        return result;
+    }
+}

+ 4 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/LimitRangeVO.java

@@ -0,0 +1,4 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+public class LimitRangeVO {
+}

+ 0 - 65
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Namespace.java

@@ -1,65 +0,0 @@
-package nju.seec.SEECdemo.logic.api.k8s.vo;
-
-import io.kubernetes.client.models.*;
-import lombok.Data;
-import lombok.ToString;
-import org.springframework.util.CollectionUtils;
-
-import javax.validation.constraints.NotNull;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * author: rale
- * createdAt: 12/22/18
- */
-@ToString
-@Data
-public class Namespace {
-
-    private static final String KIND = "Namespace";
-
-    private static final String VERSION = "v1";
-    @NotNull
-    private String name;
-
-    private String description;
-
-    @NotNull
-    private String type;
-
-    public V1Namespace toV1Namespace(){
-        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
-                .withName(name)
-                .withLabels(Collections.singletonMap(Constants.TYPE_LABEL, type))
-                .addToAnnotations(Constants.DESCRIPTION_LABEL, description == null ? name : description)
-                .build();
-        V1NamespaceSpec v1NamespaceSpec = new V1NamespaceSpecBuilder()
-                .build();
-        V1Namespace v1Namespace = new V1NamespaceBuilder()
-                .withApiVersion(VERSION)
-                .withKind(KIND)
-                .withMetadata(v1ObjectMeta)
-                .withSpec(v1NamespaceSpec)
-                .build();
-        return v1Namespace;
-
-    }
-
-    public Namespace fromV1Namespace(V1Namespace v1Namespace) {
-        Namespace namespace = new Namespace();
-        V1ObjectMeta v1ObjectMeta = v1Namespace.getMetadata();
-        if (v1ObjectMeta != null) {
-            namespace.setName(v1Namespace.getMetadata().getName());
-            Map<String, String> annotations = v1ObjectMeta.getAnnotations();
-            if (!CollectionUtils.isEmpty(annotations)) {
-                namespace.setDescription(annotations.get(Constants.DESCRIPTION_LABEL));
-            }
-            Map<String, String> labels = v1ObjectMeta.getLabels();
-            if (!CollectionUtils.isEmpty(labels)) {
-                namespace.setType(labels.get(Constants.TYPE_LABEL));
-            }
-        }
-        return namespace;
-    }
-}

+ 43 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/SecretVO.java

@@ -0,0 +1,43 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.models.V1Secret;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nju.seec.SEECdemo.logic.api.k8s.util.SecretTypeEnum;
+import org.springframework.util.CollectionUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+@Data
+@NoArgsConstructor
+public class SecretVO {
+
+    private String name;
+
+    private String namespace;
+
+    private Map<String, String> data;
+
+    private SecretTypeEnum secretType;
+
+    public SecretVO(V1Secret v1Secret) {
+        this.name = v1Secret.getMetadata().getName();
+        this.namespace = v1Secret.getMetadata().getNamespace();
+        this.data = new HashMap<>();
+        if (!CollectionUtils.isEmpty(v1Secret.getStringData())) {
+            data.putAll(v1Secret.getStringData());
+        }
+        if (!CollectionUtils.isEmpty(v1Secret.getData())) {
+            for (Map.Entry<String, byte[]> entry : v1Secret.getData().entrySet()){
+                data.put(entry.getKey(), new String(entry.getValue(), StandardCharsets.UTF_8));
+            }
+        }
+        this.secretType = SecretTypeEnum.getByValue(v1Secret.getType());
+    }
+}

+ 8 - 0
src/main/java/nju/seec/SEECdemo/logic/exception/ServiceException.java

@@ -0,0 +1,8 @@
+package nju.seec.SEECdemo.logic.exception;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+public class ServiceException {
+}

+ 10 - 6
src/main/java/nju/seec/SEECdemo/logic/service/ApplicationService.java

@@ -8,11 +8,10 @@ public interface ApplicationService {
     /**
      * 新建应用
      * @todo 挂载
-     * @param projectId 项目ID
      * @param applicationDTO 应用详情
      * @return
      */
-    ApplicationVO create(long projectId, ApplicationDTO applicationDTO);
+    ApplicationVO create(ApplicationDTO applicationDTO);
 
     /**
      * 删除应用
@@ -24,17 +23,22 @@ public interface ApplicationService {
     /**
      * 更新应用
      * 可更新的内容包括应用的镜像,应用的资源
-     * @param projectId
      * @param applicationDTO
      * @return
      */
-    ApplicationVO update(long projectId, ApplicationDTO applicationDTO);
+    ApplicationVO update(ApplicationDTO applicationDTO);
 
     /**
      * 查看应用的情况
-     * @param projectId
+     * @param projectName
      * @param name
      * @return
      */
-    ApplicationVO getByName(long projectId, String name);
+    ApplicationVO getByName(String projectName, String name);
+
+    /**
+     * 部署项目
+     * @param applicationDTO
+     */
+    ApplicationVO deploy(ApplicationDTO applicationDTO);
 }

+ 37 - 0
src/main/java/nju/seec/SEECdemo/logic/service/DBService.java

@@ -0,0 +1,37 @@
+package nju.seec.SEECdemo.logic.service;
+
+
+import nju.seec.SEECdemo.logic.vo.DBDetailVO;
+
+public interface DBService {
+
+    /**
+     * 在公用数据库中创建schema和用户,并赋予权限
+     * @param schema
+     * @param username
+     * @param password
+     * @return
+     */
+    DBDetailVO createSchemaAndUser(String schema, String username, String password);
+
+    /**
+     * 在公用数据库中随机创建schema和用户,并赋予权限
+     * @return
+     */
+    DBDetailVO createSchemaAndUserForApplication(String projectId, String applicationName);
+
+    /**
+     * 在公用数据库中随机创建schema和用户,并赋予权限
+     * @warning 用该API创建的schema名称即为projectId, 即一个project下只能有一个schema
+     * @param projectId
+     * @return
+     */
+    DBDetailVO createSchemaAndUserForProject(String projectId);
+
+    /**
+     * @param schema
+     */
+    void deleteSchema(String schema);
+
+    void deleteUser(String user);
+}

+ 6 - 2
src/main/java/nju/seec/SEECdemo/logic/service/ProjectService.java

@@ -2,20 +2,24 @@ package nju.seec.SEECdemo.logic.service;
 
 import nju.seec.SEECdemo.logic.vo.ProjectVO;
 
+import java.time.LocalDate;
 import java.util.Date;
 
 public interface ProjectService {
 
     /**
      * 创建软工二的项目
+     * 项目名称需要具有唯一性,如果项目名称冲突,会抛出异常
      * //@Todo 后期希望能够根据课程类型选择课程模板
      * @param projectName
      * @param description
      * @param startsAt
      * @param endsAt
-     * @return
+     * @return 项目创建详情
+     * @exception nju.seec.SEECdemo.logic.exception.ServiceException
      */
-     long createSeecIIProject(String projectName, String description, Date startsAt, Date endsAt);
+     ProjectVO createSeecIIProject(String projectName, String description, LocalDate startsAt, LocalDate endsAt);
+
 
     /**
      * 删除项目

+ 130 - 7
src/main/java/nju/seec/SEECdemo/logic/service/impl/ApplicationServiceImpl.java

@@ -1,25 +1,46 @@
 package nju.seec.SEECdemo.logic.service.impl;
 
-import io.kubernetes.client.apis.CoreV1Api;
-import io.kubernetes.client.models.*;
-import nju.seec.SEECdemo.logic.api.k8s.DeploymentApi;
+import nju.seec.SEECdemo.logic.api.k8s.*;
+import nju.seec.SEECdemo.logic.api.k8s.model.Deployment;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress;
+import nju.seec.SEECdemo.logic.api.k8s.model.Service;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.util.SecretTypeEnum;
+import nju.seec.SEECdemo.logic.api.k8s.vo.SecretVO;
 import nju.seec.SEECdemo.logic.service.ApplicationService;
 import nju.seec.SEECdemo.logic.vo.ApplicationVO;
-import nju.seec.SEECdemo.logic.vo.ContainerVO;
+import nju.seec.SEECdemo.util.LoggerUtil;
 import nju.seec.SEECdemo.web.dto.ApplicationDTO;
+import nju.seec.SEECdemo.web.dto.ContainerDTO;
+import nju.seec.SEECdemo.web.dto.PortDTO;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.util.*;
 import java.util.stream.Collectors;
 
+@org.springframework.stereotype.Service
 public class ApplicationServiceImpl implements ApplicationService{
 
+    private static final Logger logger = LoggerUtil.getLogger(ApplicationServiceImpl.class);
+
     @Autowired
     private DeploymentApi deploymentApi;
 
+    @Autowired
+    private ServiceApi serviceApi;
+
+    @Autowired
+    private NamespaceApi namespaceApi;
+
+    @Autowired
+    private SecretApi secretApi;
+
+    @Autowired
+    private IngressApi ingressApi;
 
     @Override
-    public ApplicationVO create(long projectId, ApplicationDTO applicationDTO) {
+    public ApplicationVO create(ApplicationDTO applicationDTO) {
         return null;
     }
 
@@ -29,12 +50,114 @@ public class ApplicationServiceImpl implements ApplicationService{
     }
 
     @Override
-    public ApplicationVO update(long projectId, ApplicationDTO applicationDTO) {
+    public ApplicationVO update(ApplicationDTO applicationDTO) {
         return null;
     }
 
     @Override
-    public ApplicationVO getByName(long projectId, String name) {
+    public ApplicationVO getByName(String projectName, String name) {
         return null;
     }
+
+    @Override
+    public ApplicationVO deploy(ApplicationDTO applicationDTO) {
+        String projectName = applicationDTO.getProjectName();
+        if (!namespaceApi.exists(projectName)) {
+            //抛出项目不存在异常
+            return null;
+        }
+
+        try {
+            //删除现有的同名deployment
+            deploymentApi.deleteIfExist(applicationDTO.getProjectName(), applicationDTO.getName());
+            Deployment deployment = buildDeploymentDTO(applicationDTO);
+            deploymentApi.createSync(deployment);
+
+            //删除现有的同名Service
+            serviceApi.deleteIfExists(applicationDTO.getProjectName(), applicationDTO.getName());
+            Service service = buildServiceDTO(applicationDTO);
+            serviceApi.create(service);
+
+            //删除现有的同名Ingress
+            ingressApi.deleteIfExists(applicationDTO.getProjectName(), applicationDTO.getName());
+            Ingress ingress = buildIngressDTO(applicationDTO);
+            ingressApi.create(ingress);
+
+            //构建返回数据
+            ApplicationVO applicationVO = new ApplicationVO();
+            applicationVO.setName(applicationDTO.getName());
+            applicationVO.setProjectName(applicationDTO.getProjectName());
+            if (ingress.getHttpRules() != null) {
+                applicationVO.setUrl(
+                        ingress.getHttpRules().stream()
+                                .map(rule -> ingress.getHttpHost() + rule).collect(Collectors.toList())
+                );
+
+            }
+            return applicationVO;
+        } catch (K8sApiException e) {
+            //抛出创建失败异常
+        }
+        return null;
+    }
+
+    private Deployment buildDeploymentDTO(ApplicationDTO applicationDTO) {
+        Deployment deployment = applicationDTO.toDeployment();
+
+        //将registry密钥作为imagePullSecrets注入
+        List<String> registrySecrets = secretApi
+                .getSecretListByType(
+                        applicationDTO.getProjectName(),
+                        SecretTypeEnum.REGISTRY)
+                .stream()
+                .map(SecretVO::getName)
+                .collect(Collectors.toList()
+        );
+        deployment.setImagePullSecrets(registrySecrets);
+
+        //将一般密钥作为环境变量注入
+        List<SecretVO> genericSecrets = secretApi.getSecretListByType(applicationDTO.getProjectName(), SecretTypeEnum.Generic);
+        Map<String, String> env = new HashMap<>();
+        genericSecrets.forEach(secretVO -> env.putAll(secretVO.getData()));
+        deployment.getContainers().forEach(containerDTO -> containerDTO.setEnv(env));
+
+        return deployment;
+    }
+
+    private Service buildServiceDTO(ApplicationDTO applicationDTO) {
+        return applicationDTO.toService();
+    }
+
+    //@todo 构建Ingress
+    //@fixme 潜在bug 如果容器上的IP和Service上的IP并非一一对应,根据容器的IP暴露Service会出现映射错误
+    private Ingress buildIngressDTO(ApplicationDTO applicationDTO) {
+        String appName = applicationDTO.getName();
+        String projectName = applicationDTO.getProjectName();
+
+        Ingress ingress = new Ingress();
+        ingress.setName(appName);
+        ingress.setNamespace(projectName);
+        ingress.setHttpHost("deernowl.cn");
+
+        //@todo lambda表达式解决
+        Set<Ingress.IngressDetail> detailDTOS = new HashSet<>();
+        for (ContainerDTO containerDTO : applicationDTO.getContainers()) {
+            String containerName = containerDTO.getName();
+
+            Set<Ingress.IngressDetail> ingressDetails = containerDTO.getPorts()
+                    .stream()
+                    .filter(PortDTO::isExpose)
+                    .map(portDTO ->{
+                        Ingress.IngressDetail ingressDetail = new Ingress.IngressDetail();
+                        ingressDetail.setServiceName(applicationDTO.getName());
+                        ingressDetail.setPort(portDTO.getPort());
+                        ingressDetail.setPath("/" + appName + "/" + containerName);
+                        return ingressDetail;
+                    })
+                    .collect(Collectors.toSet());
+            detailDTOS.addAll(ingressDetails);
+        }
+        ingress.setHttpRules(detailDTOS);
+        return ingress;
+    }
 }

+ 122 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/DBServiceImpl.java

@@ -0,0 +1,122 @@
+package nju.seec.SEECdemo.logic.service.impl;
+
+import nju.seec.SEECdemo.logic.api.k8s.ExecApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.service.DBService;
+import nju.seec.SEECdemo.logic.vo.DBDetailVO;
+import nju.seec.SEECdemo.util.ApplicationProperties;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import nju.seec.SEECdemo.util.RandomStringUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class DBServiceImpl implements DBService{
+
+    private static final Logger logger = LoggerUtil.getLogger(DBServiceImpl.class);
+
+    private final ExecApi execApi;
+
+    private final String publicMySqlPod;
+
+    private final String publicMysqlUsername;
+
+    private final String publicMysqlPassword;
+
+    private final String publicMysqlUrl;
+
+    private final String publicNamespace;
+    @Autowired
+    public DBServiceImpl(ApplicationProperties applicationProperties, ExecApi execApi) {
+        this.execApi = execApi;
+        ApplicationProperties.K8s k8s = applicationProperties.getK8s();
+        this.publicMySqlPod = k8s.getPublicMysqlPod();
+        this.publicMysqlUsername = k8s.getPublicMysqlUsername();
+        this.publicMysqlPassword = k8s.getPublicMysqlPassword();
+        this.publicMysqlUrl = k8s.getPublicMysqlUrl();
+        this.publicNamespace = k8s.getPublicNamespace();
+    }
+
+    @Override
+    public DBDetailVO createSchemaAndUser(String schema, String username, String password) {
+        DBDetailVO dbDetailVO = new DBDetailVO(publicMysqlUrl, schema, username, password);
+
+        StringBuilder args = new StringBuilder();
+        args.append("mysql -u ").append(publicMysqlUsername).append(" -p").append(publicMysqlPassword).append(" -e ")
+                .append("\"drop database if exists ").append(schema).append(";")
+                .append("create database ").append(schema).append(";")
+                .append("drop user if exists ").append(username).append(";")
+                .append("create user ").append(username).append(" identified by '").append(password).append("';")
+                .append("grant all privileges on ").append(schema).append(".* to ").append(username).append("@'%' identified by '").append(password).append("';")
+                .append("\"");
+        List<String> commands = new ArrayList<>();
+        commands.add("sh");
+        commands.add("-c");
+        commands.add(args.toString());
+        try {
+            execApi.exec(publicNamespace, publicMySqlPod, commands);
+        } catch (K8sApiException e) {
+        }
+        return dbDetailVO;
+    }
+
+    @Override
+    public DBDetailVO createSchemaAndUserForApplication(String projectId, String applicationName) {
+        String schemaName = projectId + "_" + applicationName + "_db";
+        String userName = projectId + applicationName;
+        String password = RandomStringUtil.getRandomString(10);
+        LoggerUtil.info(logger,"创建数据库信息 schema={}, userName={}, password={}",
+                schemaName, userName, password);
+        return createSchemaAndUser(schemaName, userName, password);
+    }
+
+    @Override
+    public DBDetailVO createSchemaAndUserForProject(String projectId) {
+        String schemaName = projectId + "_db";
+        String userName = projectId;
+        String password = RandomStringUtil.getRandomString(10);
+        LoggerUtil.info(logger,"创建数据库信息 schema={}, userName={}, password={}",
+                schemaName, userName, password);
+        return createSchemaAndUser(schemaName, userName, password);
+    }
+
+    @Override
+    public void deleteSchema(String schema) {
+        StringBuilder args = new StringBuilder();
+        args.append("mysql -u ").append(publicMysqlUsername).append(" -p").append(publicMysqlPassword).append(" -e ")
+                .append("\"drop database if exists ").append(schema).append(";")
+                .append("\"");
+        LoggerUtil.info(logger,"删除数据库 schema={}", schema);
+        List<String> commands = new ArrayList<>();
+        commands.add("sh");
+        commands.add("-c");
+        commands.add(args.toString());
+        try {
+            execApi.exec(publicNamespace, publicMySqlPod, commands);
+        } catch (K8sApiException e) {
+            LoggerUtil.error(logger, e, "删除数据库失败,schema={}, commands={}", schema, commands);
+        }
+    }
+
+    @Override
+    public void deleteUser(String user) {
+        StringBuilder args = new StringBuilder();
+        args.append("mysql -u ").append(publicMysqlUsername).append(" -p").append(publicMysqlPassword).append(" -e ")
+                .append("drop user if exists ").append(user).append(";")
+                .append("\"");
+        List<String> commands = new ArrayList<>();
+        commands.add("sh");
+        commands.add("-c");
+        commands.add(args.toString());
+        try {
+            execApi.exec(publicNamespace, publicMySqlPod, commands);
+        } catch (K8sApiException e) {
+            LoggerUtil.error(logger, e, "删除用户失败,user={}, commands={}", user, commands);
+        }
+    }
+
+}

+ 129 - 8
src/main/java/nju/seec/SEECdemo/logic/service/impl/ProjectServiceImpl.java

@@ -1,27 +1,109 @@
 package nju.seec.SEECdemo.logic.service.impl;
 
-import io.kubernetes.client.ApiException;
-import io.kubernetes.client.apis.CoreV1Api;
-import io.kubernetes.client.models.V1Namespace;
-import io.kubernetes.client.models.V1ObjectMeta;
+import io.kubernetes.client.models.V1LimitRange;
+import nju.seec.SEECdemo.data.entity.ResourceConfig;
+import nju.seec.SEECdemo.logic.api.k8s.LimitRangeApi;
+import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
+import nju.seec.SEECdemo.logic.api.k8s.ResourceQuotaApi;
+import nju.seec.SEECdemo.logic.api.k8s.SecretApi;
+import nju.seec.SEECdemo.logic.api.k8s.model.Namespace;
+import nju.seec.SEECdemo.logic.api.k8s.model.ResourceQuota;
+import nju.seec.SEECdemo.logic.service.DBService;
 import nju.seec.SEECdemo.logic.service.ProjectService;
+import nju.seec.SEECdemo.logic.vo.DBDetailVO;
 import nju.seec.SEECdemo.logic.vo.ProjectVO;
+import nju.seec.SEECdemo.logic.vo.projecttemplate.ProjectTemplate;
+import nju.seec.SEECdemo.util.ApplicationProperties;
+import nju.seec.SEECdemo.util.DateUtil;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.Date;
+import java.time.LocalDate;
 import java.util.HashMap;
 import java.util.Map;
 
+import static nju.seec.SEECdemo.util.Constants.*;
+
 @Service
 public class ProjectServiceImpl implements ProjectService{
+
+    private static final Logger logger = LoggerUtil.getLogger(ProjectService.class);
+
+    @Autowired
+    private NamespaceApi namespaceApi;
+
+    @Autowired
+    private ResourceQuotaApi resourceQuotaApi;
+
+    @Autowired
+    private ProjectTemplate seecIITemplate;
+
+    @Autowired
+    private SecretApi secretApi;
+
+    @Autowired
+    private DBService dbService;
+
     @Autowired
-    private CoreV1Api api;
+    private ApplicationProperties applicationProperties;
 
+    @Autowired
+    private LimitRangeApi limitRangeApi;
 
     @Override
-    public long createSeecIIProject(String projectName, String description, Date startsAt, Date endsAt) {
-        return 0;
+    public ProjectVO createSeecIIProject(String projectName, String description, LocalDate startsAt, LocalDate endsAt) {
+        if (namespaceApi.exists(projectName)) {
+            LoggerUtil.error(logger, "项目已经存在, projectName={}", projectName);
+            return null;
+        }
+
+        try {
+            //创建部署空间
+            String type = seecIITemplate.getName();
+            Namespace namespace = buildNamespace(projectName, type, description, startsAt, endsAt);
+            namespaceApi.create(namespace);
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "创建项目失败,projectName={}, projectType={}", projectName, seecIITemplate.getName());
+            return null;
+        }
+
+        try {
+            //为部署空间配置私有仓库密钥
+            ApplicationProperties.Registry registry = applicationProperties.getRegistry();
+            secretApi.createPrivateRegistrySecret(projectName, registry.getHost(), registry.getUsername(), registry.getPassword());
+
+            //创建项目资源限额
+            ResourceQuota resourceQuota = buildResourceQuota(projectName, seecIITemplate.getDefaultResourceConfig());
+            resourceQuotaApi.create(resourceQuota);
+
+            //创建项目资源默认配额
+            V1LimitRange v1LimitRange = buildLimitRange(projectName, seecIITemplate.getDefaultResourceConfig());
+            limitRangeApi.createLimitRange(projectName, v1LimitRange);
+
+            //创建项目同名数据库
+            DBDetailVO dbDetailVO = dbService.createSchemaAndUserForProject(projectName);
+            Map<String, byte[]> data = new HashMap<>();
+            data.put("DB_URL", dbDetailVO.getUrl().getBytes());
+            data.put("DB_SCHEMA", dbDetailVO.getSchema().getBytes());
+            data.put("DB_USERNAME", dbDetailVO.getUsername().getBytes());
+            data.put("DB_PASSWORD", dbDetailVO.getPassword().getBytes());
+            secretApi.createGenericSecret(projectName, projectName + "-" + DB_SECRET_NAME, data);
+
+            //封装返回数据
+            ProjectVO projectVO = new ProjectVO();
+            projectVO.setName(projectName);
+            projectVO.setDescription(description);
+            projectVO.setStartsAt(DateUtil.fromDateToString(startsAt));
+            projectVO.setEndsAt(DateUtil.fromDateToString(endsAt));
+            projectVO.getExtra().put(ProjectVO.DB, dbDetailVO);
+            return projectVO;
+        } catch (Exception e) {
+            LoggerUtil.error(logger, e, "配置项目失败,projectName={}, projectType={}", projectName, seecIITemplate.getName());
+            namespaceApi.delete(projectName);
+        }
+        return null;
     }
 
     @Override
@@ -33,4 +115,43 @@ public class ProjectServiceImpl implements ProjectService{
     public boolean deleteProjectForce(long projectId) {
         return false;
     }
+
+    /**
+     * 根据项目信息构建Namespace
+     * @param projectName Namespace的名称
+     * @param type 项目的类型,存在Namespace的标签中
+     * @param description 项目的描述,可以存在Namespace的标签或者注释中
+     * @param startsAt 项目开始时间,存在Namespace的标签中
+     * @param endsAt 项目的结束时间,存在Namespace的标签中
+     * @return
+     */
+    private Namespace buildNamespace(String projectName, String type, String description, LocalDate startsAt, LocalDate endsAt){
+        Namespace namespace = new Namespace();
+        namespace.setName(projectName);
+        namespace.addToLabel(TYPE_LABEL, type);
+        namespace.addToLabel(DESCRIPTION_LABEL, description);
+        namespace.addToLabel(STARTS_AT_LABEL, DateUtil.fromDateToString(startsAt, DateUtil.BASE_DATE_FORMAT));
+        namespace.addToLabel(ENDS_AT_LABEL, DateUtil.fromDateToString(endsAt, DateUtil.BASE_DATE_FORMAT));
+        return namespace;
+    }
+
+    private ResourceQuota buildResourceQuota(String projectName, ResourceConfig resourceConfig) {
+        ResourceQuota resourceQuota = new ResourceQuota();
+        resourceQuota.setNamespace(projectName);
+        resourceQuota.setName(projectName);
+        resourceQuota.setMaxMemoryTotalRequest(resourceConfig.getMaxMemory());
+        resourceQuota.setMaxMemoryTotalLimit(resourceConfig.getMaxMemory());
+        resourceQuota.setMaxCpuTotalRequest(resourceConfig.getMaxCPU());
+        resourceQuota.setMaxCpuTotalLimit(resourceConfig.getMaxCPU());
+        return resourceQuota;
+    }
+
+    private V1LimitRange buildLimitRange(String projectName, ResourceConfig resourceConfig) {
+        //配置应用的默认配额
+        Map<String, Integer> defaultValue = new HashMap<>();
+        defaultValue.put("cpu", 50);
+        defaultValue.put("memory", 500);
+        V1LimitRange v1LimitRange = limitRangeApi.toLimitRange(projectName, projectName, defaultValue, defaultValue, null, null);
+        return v1LimitRange;
+    }
 }

+ 7 - 3
src/main/java/nju/seec/SEECdemo/logic/vo/ApplicationVO.java

@@ -3,17 +3,21 @@ package nju.seec.SEECdemo.logic.vo;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Data
 @EqualsAndHashCode
 public class ApplicationVO {
 
+    private String projectName;
 
-    private long id;
+    private String name;
 
-    private long projectId;
+    private List<String> url;
 
-    private String name;
+    //传递回额外的信息
+    private Map<String, Object> extra = new HashMap<>();
 
 }

+ 1 - 2
src/main/java/nju/seec/SEECdemo/logic/vo/ContainerVO.java

@@ -1,7 +1,6 @@
 package nju.seec.SEECdemo.logic.vo;
 
 import lombok.Data;
-import nju.seec.SEECdemo.data.entity.Port;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -20,5 +19,5 @@ public class ContainerVO {
 
     private Map<String, String> env = new HashMap<>();
 
-    private Set<Port> ports;
+    private Set<PortVO> ports;
 }

+ 24 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/DBDetailVO.java

@@ -0,0 +1,24 @@
+package nju.seec.SEECdemo.logic.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class DBDetailVO {
+
+    private String url;
+
+    private String schema;
+
+    private String username;
+
+    private String password;
+
+    public DBDetailVO(String url, String schema, String username, String password) {
+        this.url = url;
+        this.schema = schema;
+        this.username = username;
+        this.password = password;
+    }
+}

+ 29 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/PortVO.java

@@ -0,0 +1,29 @@
+package nju.seec.SEECdemo.logic.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode
+public class PortVO {
+
+    @NotNull
+//    @Pattern(regexp = NAME_PATTERN)
+    private String name;
+
+    @NotNull
+    @Range(min = 1, max = 65535)
+    private int port;
+
+
+    @NotNull
+    @Pattern(regexp = "^(TCP|UDP)$")
+    private String protocol;
+
+}

+ 10 - 4
src/main/java/nju/seec/SEECdemo/logic/vo/ProjectVO.java

@@ -3,19 +3,25 @@ package nju.seec.SEECdemo.logic.vo;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.time.LocalDate;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 @Data
 @EqualsAndHashCode
 public class ProjectVO {
-
-    private Long id;
+    public static final String DB = "DB";
 
     private String name;
 
     private String description;
 
-    private Date startsAt;
+    private String startsAt;
+
+    private String endsAt;
+
+    private Map<String, Object> extra = new HashMap<>();
+
 
-    private Date endsAt;
 }

+ 8 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/WebApplicationVO.java

@@ -0,0 +1,8 @@
+package nju.seec.SEECdemo.logic.vo;
+
+/**
+ * author: rale
+ * createdAt: 12/22/18
+ */
+public class WebApplicationVO {
+}

+ 2 - 6
src/main/java/nju/seec/SEECdemo/logic/vo/projecttemplate/ProjectTemplate.java

@@ -2,11 +2,7 @@ package nju.seec.SEECdemo.logic.vo.projecttemplate;
 
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import nju.seec.SEECdemo.data.entity.resource.ResourceConfig;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
+import nju.seec.SEECdemo.data.entity.ResourceConfig;
 
 @Data
 @EqualsAndHashCode
@@ -20,5 +16,5 @@ public class ProjectTemplate {
 
     private ResourceConfig defaultResourceConfig;
 
-    private List<Function> operations = new ArrayList<>();
 }
+

+ 24 - 0
src/main/java/nju/seec/SEECdemo/util/ApplicationProperties.java

@@ -35,6 +35,8 @@ public class ApplicationProperties {
 	private Storage storage = new Storage();
 	@Valid
 	private K8s k8s = new K8s();
+    @Valid
+    private Registry registry = new Registry();
 
 	@Data
 	public static class Gitlab {
@@ -100,5 +102,27 @@ public class ApplicationProperties {
 		private String token;
 		@NotEmpty
 		private String ingressHost;
+		@NotEmpty
+		private String publicNamespace;
+		@NotEmpty
+		private String publicMysqlUrl;
+		@NotEmpty
+		private String publicMysqlPod;
+		@NotEmpty
+		private String publicMysqlUsername;
+
+		private String publicMysqlPassword;
 	}
+
+	@Data
+    public static class Registry{
+	    @NotEmpty
+	    private String host;
+
+	    @NotEmpty
+	    private String username;
+
+	    @NotEmpty
+	    private String password;
+    }
 }

+ 33 - 0
src/main/java/nju/seec/SEECdemo/util/BeanUtil.java

@@ -0,0 +1,33 @@
+package nju.seec.SEECdemo.util;
+
+import nju.seec.SEECdemo.data.entity.ResourceConfig;
+import nju.seec.SEECdemo.logic.api.k8s.model.CpuQuantity;
+import nju.seec.SEECdemo.logic.api.k8s.model.StorageQuantity;
+import nju.seec.SEECdemo.logic.vo.projecttemplate.ProjectTemplate;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+@Component
+public class BeanUtil {
+
+    /**
+     * 软工二的项目模板
+     * @return
+     */
+    @Bean
+    public ProjectTemplate seecIITemplate() {
+        ProjectTemplate projectTemplate = new ProjectTemplate();
+        ResourceConfig resourceConfig = new ResourceConfig();
+        resourceConfig.setDefaultCPU(new CpuQuantity(40));
+        resourceConfig.setMaxCPU(new CpuQuantity(50));
+        resourceConfig.setDefaultMemory(new StorageQuantity(500));
+        resourceConfig.setMaxMemory(new StorageQuantity(600));
+        resourceConfig.setDefaultStorage(StorageQuantity.EMPTY);
+        resourceConfig.setMaxStorage(StorageQuantity.EMPTY);
+        projectTemplate.setDefaultResourceConfig(resourceConfig);
+        projectTemplate.setName("SeecII");
+        projectTemplate.setDescription("SeecII-demo");
+
+        return projectTemplate;
+    }
+}

+ 42 - 0
src/main/java/nju/seec/SEECdemo/util/Constants.java

@@ -0,0 +1,42 @@
+package nju.seec.SEECdemo.util;
+
+public abstract class Constants {
+
+    public static final String NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
+
+    //标签域名前缀
+    private static final String LABEL_PREFIX = "demo.seec.nju.cn";
+    //课程前缀
+    public static final String TYPE_LABEL = LABEL_PREFIX + "/type";
+    //描述
+    public static final String DESCRIPTION_LABEL = LABEL_PREFIX + "/description";
+    //应用
+    public static final String APPLICATION_LABEL = LABEL_PREFIX + "/app";
+    // 用户namespace的label
+    public static final String USERSPACE_LABEL = LABEL_PREFIX +"/userspace";
+    // pod的label
+    public static final String SELECTOR_LABEL = LABEL_PREFIX + "/app";
+    // 非对外service的label
+    public static final String INTERNAL_LABEL = LABEL_PREFIX + "/internal";
+    // 对外service的label
+    public static final String EXTERNAL_LABEL = LABEL_PREFIX + "/external";
+    // 持久卷访问模式
+    public static final String STORAGE_ACCESS_MODE = "ReadWriteMany";
+    // 持久卷存储类型
+    public static final String STORAGE_CLASS = "glusterfs";
+
+    public static final String RESOURCE_STORAGE = "storage";
+
+    public static final String STARTS_AT_LABEL = LABEL_PREFIX + "/start";
+
+    public static final String ENDS_AT_LABEL = LABEL_PREFIX + "/end";
+
+    public static final String PRETTY_FORMAT = "true";
+
+    //前台清除策略
+    public static final String FOREGROUND_PROPAGATION_POLICY = "Foreground";
+
+    public static final String METADATA_NAME_SELECTOR = "metadata.name=";
+
+    public static final String DB_SECRET_NAME = "db-secret";
+}

+ 54 - 0
src/main/java/nju/seec/SEECdemo/util/DateUtil.java

@@ -0,0 +1,54 @@
+package nju.seec.SEECdemo.util;
+
+import org.joda.time.DateTime;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+public class DateUtil {
+
+    public static final String BASE_DATE_FORMAT = "yyyy-MM-dd";
+    public static final String BASE_MONTH_DAY_FORMAT_CHINESE = "MM月dd日";
+
+    public static final String BASE_DATE_TIME_FORMAT = "yyyy-MM-dd hh:MM:ss";
+    public static final String BASE_DATE_TIME_FORMAT_WITHOUT_SECONDS = "yyyy-MM-dd HH:mm";
+
+    public static String fromDateToString(LocalDate date) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BASE_DATE_FORMAT);
+        return date.format(formatter);
+    }
+
+    public static String fromDateToString(LocalDate date, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        return date.format(formatter);
+    }
+
+    public static LocalDate fromStringToDate(String dateString, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        return LocalDate.parse(dateString, formatter);
+    }
+
+    public static LocalDateTime fromStringToDatetime(String dataTimeString, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        return LocalDateTime.parse(dataTimeString, formatter);
+    }
+
+    public static String fromDateTimeToString(LocalDateTime date, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        return date.format(formatter);
+    }
+
+    public static LocalDateTime fromJodaDateTime(DateTime dateTime) {
+        return LocalDateTime.of(
+                dateTime.getYear(),
+                dateTime.getMonthOfYear(),
+                dateTime.getDayOfMonth(),
+                dateTime.getHourOfDay(),
+                dateTime.getMinuteOfHour(),
+                dateTime.getSecondOfMinute());
+    }
+
+
+}

+ 5 - 0
src/main/java/nju/seec/SEECdemo/util/LoggerUtil.java

@@ -22,6 +22,11 @@ public class LoggerUtil {
         logger.warn(info, parameters);
     }
 
+    public static void warn(Logger logger, Exception e, String info, Object... parameters) {
+        logger.warn("", e);
+        logger.warn(info, parameters);
+    }
+
     public static void  error(Logger logger, String info, Object... parameters) {
         logger.error(info, parameters);
     }

+ 23 - 0
src/main/java/nju/seec/SEECdemo/util/RandomStringUtil.java

@@ -0,0 +1,23 @@
+package nju.seec.SEECdemo.util;
+
+import java.util.Random;
+
+/**
+ * 生成随机的字符串
+ */
+public class RandomStringUtil {
+
+    private static final String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    private static final Random random = new Random();
+
+    public static String getRandomString(int length) {
+        StringBuffer sb = new StringBuffer();
+
+        for (int i = 0; i < length; ++i) {
+            int number = random.nextInt(str.length());// [0,62)
+            sb.append(str.charAt(number));
+        }
+        return sb.toString();
+    }
+}

+ 43 - 2
src/main/java/nju/seec/SEECdemo/web/dto/ApplicationDTO.java

@@ -1,10 +1,15 @@
 package nju.seec.SEECdemo.web.dto;
 
 import lombok.Data;
+import nju.seec.SEECdemo.logic.api.k8s.model.Deployment;
+import nju.seec.SEECdemo.logic.api.k8s.model.Service;
 import org.hibernate.validator.constraints.Range;
 
 import javax.validation.constraints.NotEmpty;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static nju.seec.SEECdemo.util.Constants.APPLICATION_LABEL;
 
 @Data
 public class ApplicationDTO {
@@ -12,14 +17,50 @@ public class ApplicationDTO {
     @NotEmpty
     private String name;
 
+    @NotEmpty
+    private String projectName;
+
     @Range(min = 1, max = 3)
     private int replicas;
 
-    private List<ContainerDTO> containers;
+    private List<ContainerDTO> containers = new ArrayList<>();
 
+    @Range(min = 0, max = 100)
     private int cpuRequest;
 
+    /**
+     * 存储的默认单位为M
+     */
+    @Range(min = 0)
     private int memoryRequest;
 
+    @Range(min = 0)
     private int storageRequest;
+
+    public Deployment toDeployment(){
+        Deployment deployment = new Deployment();
+        deployment.setNamespace(projectName);
+        deployment.setName(name);
+        deployment.setContainers(containers.stream().map(ContainerDTO::toContainerDTO).collect(Collectors.toList()));
+        deployment.setReplicas(replicas);
+        deployment.setLabels(Collections.singletonMap(APPLICATION_LABEL, name));
+        //@todo 设置资源限额
+        return deployment;
+    }
+
+    public Service toService() {
+        Service service = new Service();
+        service.setNamespace(projectName);
+        service.setName(name);
+        service.setSelectors(Collections.singletonMap(APPLICATION_LABEL, name));
+        service.setServicePorts(
+                containers
+                        .stream()
+                        .map(ContainerDTO::getPorts)
+                        .flatMap(Collection::stream)
+                        .map(PortDTO::toServicePortDTO)
+                        .collect(Collectors.toSet())
+        );
+        return service;
+    }
 }

+ 13 - 2
src/main/java/nju/seec/SEECdemo/web/dto/ContainerDTO.java

@@ -1,19 +1,30 @@
 package nju.seec.SEECdemo.web.dto;
 
 import lombok.Data;
+import nju.seec.SEECdemo.logic.api.k8s.model.Container;
 
 import javax.validation.constraints.NotEmpty;
-import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @Data
 public class ContainerDTO {
 
+    @NotEmpty
+    private String name;
+
     @NotEmpty
     private String image;
 
     private Set<PortDTO> ports = new HashSet<>();
 
+    public Container toContainerDTO() {
+        Container container = new Container();
+        container.setName(name);
+        container.setImage(image);
+        container.setPorts(ports.stream().map(PortDTO::toContainerPortDTO).collect(Collectors.toSet()));
+        return container;
+    }
+
 }

+ 42 - 3
src/main/java/nju/seec/SEECdemo/web/dto/PortDTO.java

@@ -1,12 +1,51 @@
 package nju.seec.SEECdemo.web.dto;
 
-public class PortDTO {
+import lombok.Data;
+import nju.seec.SEECdemo.logic.api.k8s.model.ContainerPort;
+import nju.seec.SEECdemo.logic.api.k8s.model.ServicePort;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.Pattern;
 
-    private String name;
+@Data
+public class PortDTO {
 
+    /**
+     * 容器暴露的端口号
+     */
+    @Range(min = 1, max = 65535)
     private int port;
 
-    private int targetPort;
+    /**
+     * 是否需要暴露在外网访问
+     * 暴露则需要分配外部访问的IP
+     */
+    private boolean expose;
+
+    /**
+     * 协议
+     */
+    @Pattern(regexp = "^(TCP|UDP)$")
+    private String protocol;
+
+    public ContainerPort toContainerPortDTO(){
+        ContainerPort containerPort = new ContainerPort();
+        containerPort.setName(protocol.toLowerCase() + "-" + port);
+        containerPort.setPort(port);
+        containerPort.setProtocol(protocol);
+        return containerPort;
+    }
 
+    /**
+     * @warning 目前还没有考虑端口重合的问题
+     * @return
+     */
+    public ServicePort toServicePortDTO() {
+        ServicePort servicePort = new ServicePort();
+        servicePort.setName(protocol.toLowerCase() + "-" + port + "-" + port);
+        servicePort.setPort(port);
+        servicePort.setTargetPort(port);
+        return servicePort;
+    }
 
 }

+ 10 - 1
src/main/resources/application-dev.yml

@@ -71,4 +71,13 @@ moocoder:
   k8s:
     api-server: https://10.1.1.198:6443
     token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi0yZ2o5NSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjRjZGQ0MGFlLTA1OWItMTFlOS1hMTIwLTAyMDA2MTI3MDAwNSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.LwQ-LQlA_yhtndGLpveRfs0ZFFW971P1tchzJZP81s1vz0msb0q6dLJmcsCHaYhHWZ58_DCc0e7suGmUFEeMZcJL-sj3js1Cp5WNdnyz99Y3vZ-krHvNHGoc42jOrNUeO27Kb7uTDCmmKcCJSQ9gw8s2ie--7Z_kPAKVjhqr9nHtfrmmuIqFgwbIBQrNyJLMxaXs6tsCS4YWBuIQK1aOPWsijJNXA8W1acQ7p95P3tBGYMq17AaOEdeK8m76CIWeIt1mzRmjAhWQ--06J2-zw1Xq62ogjQLnjZuS8Tq7G_wLeoAVo9llR72FNDBXuJo-eSdVKBrFP6-Q-nCaWcQkCw
-    ingress-host: deernowl.cn
+    ingress-host: deernowl.cn
+    public-namespace: group24
+    public-mysql-url: jdbc:mysql://mysql.group24.svc.cluster.local:3306
+    public-mysql-pod: mysql-7b688cf578-l7pxs
+    public-mysql-username: root
+    public-mysql-password: password
+  registry:
+    host: 10.1.1.243:18082
+    username: admin
+    password: admin123

+ 48 - 0
src/test/java/nju/seec/SEECdemo/service/ApplicationServiceImplTest.java

@@ -0,0 +1,48 @@
+package nju.seec.SEECdemo.service;
+
+import nju.seec.SEECdemo.logic.service.ApplicationService;
+import nju.seec.SEECdemo.web.dto.ApplicationDTO;
+import nju.seec.SEECdemo.web.dto.ContainerDTO;
+import nju.seec.SEECdemo.web.dto.PortDTO;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * author: rale
+ * createdAt: 1/3/19
+ */
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ApplicationServiceImplTest {
+
+    @Autowired
+    private ApplicationService applicationService;
+
+    @Test
+    public void testDeploy(){
+        ApplicationDTO applicationDTO = new ApplicationDTO();
+        applicationDTO.setName("test");
+        applicationDTO.setProjectName("demo");
+        applicationDTO.setReplicas(1);
+        ContainerDTO containerDTO = new ContainerDTO();
+        containerDTO.setName("demo");
+        containerDTO.setImage("10.1.1.243:18082/container_demo:1.0");
+        PortDTO portDTO = new PortDTO();
+        portDTO.setExpose(true);
+        portDTO.setPort(8081);
+        portDTO.setProtocol("TCP");
+        Set<PortDTO> ports = new HashSet<>();
+        ports.add(portDTO);
+        containerDTO.setPorts(ports);
+        applicationDTO.setContainers(Collections.singletonList(containerDTO));
+        applicationService.deploy(applicationDTO);
+    }
+}

+ 26 - 0
src/test/java/nju/seec/SEECdemo/service/DbServiceImplTest.java

@@ -0,0 +1,26 @@
+package nju.seec.SEECdemo.service;
+
+import nju.seec.SEECdemo.logic.service.DBService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class DbServiceImplTest {
+
+    @Autowired
+    private DBService dbService;
+
+    @Test
+    public void testCreate(){
+        dbService.createSchemaAndUser("java2", "rale", "what");
+    }
+
+    @Test
+    public void testCreateForProject(){
+        dbService.createSchemaAndUserForApplication("group24", "container");
+    }
+}

+ 30 - 0
src/test/java/nju/seec/SEECdemo/service/ProjectServiceImplTest.java

@@ -0,0 +1,30 @@
+package nju.seec.SEECdemo.service;
+
+import nju.seec.SEECdemo.SeecDemoApplication;
+import nju.seec.SEECdemo.logic.service.ProjectService;
+import nju.seec.SEECdemo.logic.vo.ProjectVO;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.time.LocalDate;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ProjectServiceImplTest {
+
+    @Autowired
+    private ProjectService projectService;
+
+    @Test
+    public void testCreate(){
+        ProjectVO projectVO = projectService.createSeecIIProject("demo2", "demo", LocalDate.of(2018,10,12), LocalDate.of(2019,10,15));
+        System.out.println(projectVO);
+    }
+
+    @Test
+    public void testDelete(){
+    }
+}

+ 12 - 19
src/test/java/nju/seec/SEECdemo/service/api/DeploymentApiImplTest.java

@@ -1,11 +1,9 @@
 package nju.seec.SEECdemo.service.api;
 
-import io.kubernetes.client.custom.IntOrString;
 import nju.seec.SEECdemo.logic.api.k8s.DeploymentApi;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Container;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ContainerPort;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Deployment;
-import nju.seec.SEECdemo.logic.api.k8s.vo.ServicePort;
+import nju.seec.SEECdemo.logic.api.k8s.model.Container;
+import nju.seec.SEECdemo.logic.api.k8s.model.ContainerPort;
+import nju.seec.SEECdemo.logic.api.k8s.model.Deployment;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,10 +14,8 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * author: rale
- * createdAt: 12/24/18
- */
+import static nju.seec.SEECdemo.util.Constants.APPLICATION_LABEL;
+
 @RunWith(SpringRunner.class)
 @SpringBootTest
 public class DeploymentApiImplTest {
@@ -31,25 +27,22 @@ public class DeploymentApiImplTest {
     public void testCreate() {
         Deployment deployment = new Deployment();
         deployment.setName("test");
-        deployment.setNamespace("group24");
+        deployment.setNamespace("demo");
         deployment.setReplicas(1);
 
-        ServicePort servicePort = new ServicePort();
-        servicePort.setPort(80);
-        servicePort.setTargetPort(new IntOrString(8081));
-        Set<ServicePort> servicePorts = new HashSet<>();
-        servicePorts.add(servicePort);
-        deployment.setServicePorts(servicePorts);
-
         Container container = new Container();
-        container.setName("test-container");
-        container.setImage("10.1.1.243:18082/container_demo:1.0");
+        container.setName("container");
+        container.setImage("10.1.1.243:18082/container_demo:2.0");
         ContainerPort containerPort = new ContainerPort();
         containerPort.setPort(8081);
         containerPort.setProtocol("TCP");
         Set<ContainerPort> ports = new HashSet<>();
         ports.add(containerPort);
         container.setPorts(ports);
+
+        deployment.setLabels(Collections.singletonMap(APPLICATION_LABEL, "test"));
+        deployment.setImagePullSecrets(Collections.singletonList("regcred"));
+
         deployment.setContainers(Collections.singletonList(container));
 
         deploymentApi.createSync(deployment);

+ 33 - 0
src/test/java/nju/seec/SEECdemo/service/api/ExecApiImplTest.java

@@ -0,0 +1,33 @@
+package nju.seec.SEECdemo.service.api;
+
+import nju.seec.SEECdemo.logic.api.k8s.ExecApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ExecApiImplTest {
+
+    @Autowired
+    private ExecApi execApi;
+
+    @Test
+    public void testExec(){
+        List<String> commands = new ArrayList<>();
+        commands.add("sh");
+        commands.add("-c");
+        commands.add("mysql -u root -ppassword -e 'drop if exists java; create database java;'");
+        try {
+            execApi.exec("group24", "mysql-7b688cf578-l7pxs", commands);
+        } catch (K8sApiException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 47 - 0
src/test/java/nju/seec/SEECdemo/service/api/IngressApiImplTest.java

@@ -0,0 +1,47 @@
+package nju.seec.SEECdemo.service.api;
+
+import nju.seec.SEECdemo.logic.api.k8s.IngressApi;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress;
+import nju.seec.SEECdemo.logic.api.k8s.model.Ingress.IngressDetail;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class IngressApiImplTest {
+
+    @Autowired
+    private IngressApi ingressApi;
+
+    @Test
+    public void testCreate(){
+        Ingress ingress = new Ingress();
+        ingress.setName("test");
+        ingress.setNamespace("demo");
+        ingress.setHttpHost("deernowl.cn");
+        IngressDetail ingressDetail = new IngressDetail();
+        ingressDetail.setPath("/demo/test");
+        ingressDetail.setPort(8081);
+        ingressDetail.setServiceName("test");
+        Set<IngressDetail> details = new HashSet<>();
+        details.add(ingressDetail);
+        ingress.setHttpRules(details);
+        ingressApi.create(ingress);
+
+    }
+
+    @Test
+    public void testDelete() {
+        ingressApi.delete("group24", "test-ingress");
+    }
+}

+ 152 - 0
src/test/java/nju/seec/SEECdemo/service/api/LimitRangeTest.java

@@ -0,0 +1,152 @@
+package nju.seec.SEECdemo.service.api;
+
+
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.models.V1LimitRange;
+import nju.seec.SEECdemo.logic.api.k8s.LimitRangeApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class LimitRangeTest {
+
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    private LimitRangeApi limitRangeApi;
+
+    @Test
+    public void limitRangeTest() {
+        /*V1LimitRangeItem v1LimitRangeItem = new V1LimitRangeItem();
+        Map<String, Quantity> defualt = new HashMap<>();
+        defualt.put("cpu", new CpuQuantity(100).toQuantity());
+        defualt.put("memory", new StorageQuantity(1024).toQuantity());
+        Map<String, Quantity> defualtRequest = new HashMap<>();
+        defualtRequest.put("cpu", new CpuQuantity(50).toQuantity());
+        defualtRequest.put("memory", new StorageQuantity(256).toQuantity());
+        Map<String, Quantity> max = new HashMap<>();
+        max.put("cpu", new CpuQuantity(100).toQuantity());
+        max.put("memory", new StorageQuantity(1024, StorageQuantity.Unit.GB).toQuantity());
+        Map<String, Quantity> min = new HashMap<>();
+        min.put("cpu", new CpuQuantity(50).toQuantity());
+        min.put("memory", new StorageQuantity(256).toQuantity());
+        String type = "Container";
+        String apiVersion = "v1";
+        v1LimitRangeItem = v1LimitRangeItem
+                ._default(defualt)
+                .defaultRequest(defualtRequest)
+                .max(max)
+                .min(min)
+                .type(type);
+        V1ObjectMeta meta = new V1ObjectMeta().name("limit-range-test2").namespace("mjj-test2");
+        String kind = "LimitRange";
+        V1LimitRange body = new V1LimitRange()
+                .apiVersion(apiVersion)
+                .kind(kind)
+                .metadata(meta)
+                .spec(new V1LimitRangeSpec().addLimitsItem(v1LimitRangeItem));
+        String namespace = "mjj-test2";*/
+        /*try {
+            coreV1Api.createNamespacedLimitRange(namespace, body, "OK");
+        } catch (ApiException e) {
+            System.out.println(e.getResponseBody());
+        }*/
+
+
+        //Test update
+        //LimitRange Not Found 404
+
+        /*try {
+            String body1 = "{ \"defualt\" : {\"cpu\" : 1, \"memory\": 1024Mi}}";
+            coreV1Api.patchNamespacedLimitRange("limit-range-test2", "mjj-test2", body1, "OK");
+        } catch (ApiException e) {
+            System.out.println(e.getResponseBody());
+        }*/
+
+
+        //Test createLimitRange
+        /*String name2 = "limit-range-test3";
+        Map<String, Integer> _default = new HashMap<>();
+        _default.put("cpu", 100);
+        _default.put("memory", 1024);
+        Map<String, Integer> defaultrequest = new HashMap<>();
+        defaultrequest.put("cpu", 50);
+        defaultrequest.put("memory", 512);
+        Map<String, Integer> max1 = new HashMap<>();
+        max1.put("cpu", 200);
+        max1.put("memory", 1024);
+        Map<String, Integer> min1 = new HashMap<>();
+        min1.put("cpu", 50);
+        min1.put("memory", 512);
+        String namespace2 = "mjj-test3";
+        try {
+            limitRangeApi.createLimitRange(namespace, name2, _default, defaultrequest, max1, min1);
+        } catch (K8sApiException e) {
+            System.out.println(e.getDescription());
+        }*/
+
+        //Test delete limitRange
+        /*String namespace = "mjj-test2";
+        String name = "limit-range-test3";
+        try {
+            limitRangeApi.deleteLimitRange(namespace, name);
+        } catch (K8sApiException e) {
+            e.getDescription();
+        }*/
+
+
+        //Test get LimitRange
+        /*String namespace = "mjj-test2";
+        String name = "limit-range-test2";
+        try {
+            V1LimitRange v1LimitRange = limitRangeApi.getLimitRangeByName(namespace, name);
+            System.out.println(v1LimitRange.getMetadata().getName());
+        } catch (K8sApiException e) {
+            e.printStackTrace();
+        }*/
+
+
+
+        //Test update LimitRange
+        String namespace = "mjj-test2";
+        String name = "limit-range-test2";
+        Map<String, Integer> _default = new HashMap<>();
+        _default.put("cpu", 100);
+        _default.put("memory", 1024);
+        Map<String, Integer> defaultrequest = new HashMap<>();
+        defaultrequest.put("cpu", 50);
+        defaultrequest.put("memory", 512);
+        Map<String, Integer> max1 = new HashMap<>();
+        max1.put("cpu", 200);
+        max1.put("memory", 1024);
+        Map<String, Integer> min1 = new HashMap<>();
+        min1.put("cpu", 50);
+        min1.put("memory", 512);
+        V1LimitRange body = limitRangeApi.toLimitRange(namespace, name, _default, defaultrequest, max1, min1);
+        try {
+            limitRangeApi.updateLimitRange(namespace, name, body);
+        } catch (K8sApiException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testCreateLimitRange() {
+        Map<String, Integer> _default = new HashMap<>();
+        _default.put("cpu", 100);
+        _default.put("memory", 1024);
+        V1LimitRange v1LimitRange = limitRangeApi.toLimitRange("demo", "demo", _default, _default, null, null);
+        limitRangeApi.createLimitRange("demo", v1LimitRange);
+    }
+}

+ 28 - 0
src/test/java/nju/seec/SEECdemo/service/api/LogApiImplTest.java

@@ -0,0 +1,28 @@
+package nju.seec.SEECdemo.service.api;
+
+import io.kubernetes.client.ApiException;
+import nju.seec.SEECdemo.logic.api.k8s.LogApi;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.IOException;
+
+/**
+ * author: rale
+ * createdAt: 1/6/19
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class LogApiImplTest {
+
+    @Autowired
+    private LogApi logApi;
+
+    @Test
+    public void test() throws IOException, ApiException {
+        logApi.test();
+    }
+}

+ 33 - 3
src/test/java/nju/seec/SEECdemo/service/api/NamespaceApiImplTest.java

@@ -1,13 +1,17 @@
 package nju.seec.SEECdemo.service.api;
 
 import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
-import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
+import nju.seec.SEECdemo.logic.api.k8s.model.Namespace;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * author: rale
  * createdAt: 12/22/18
@@ -21,10 +25,36 @@ public class NamespaceApiImplTest {
     private NamespaceApi namespaceApi;
 
     @Test
-    public void testCreate() {
+    public void testCreate() throws K8sApiException {
         Namespace namespace = new Namespace();
         namespace.setName("group21");
-        namespace.setDescription("这是软工二的大作业");
+        Map<String, String> labels = new HashMap<>();
+        labels.put("test1", "test1");
+        labels.put("test2", "test2");
+        namespace.setLabels(labels);
         namespaceApi.create(namespace);
     }
+
+    @Test
+    public void testDelete() throws K8sApiException {
+        namespaceApi.delete("group21");
+    }
+
+    @Test
+    public void testGetAll() throws K8sApiException {
+        namespaceApi.getAll();
+    }
+
+    @Test
+    public void testGetByLabel() throws K8sApiException {
+        namespaceApi.getByLabel("name", "group24");
+    }
+
+    @Test
+    public void testGetByLabels(){
+        Map<String, String> map = new HashMap<>();
+        map.put("demo.seec.nju.cn/description", "demo");
+        map.put("demo.seec.nju.cn/start", "2018-10-12");
+        System.out.print(namespaceApi.getByLabels(map));
+    }
 }

+ 37 - 0
src/test/java/nju/seec/SEECdemo/service/api/ResourceQuotaApiImplTest.java

@@ -0,0 +1,37 @@
+package nju.seec.SEECdemo.service.api;
+
+import nju.seec.SEECdemo.logic.api.k8s.model.CpuQuantity;
+import nju.seec.SEECdemo.logic.api.k8s.model.ResourceQuota;
+import nju.seec.SEECdemo.logic.api.k8s.model.StorageQuantity;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ResourceQuotaApiImplTest {
+
+//    @Autowired
+//    private ResourceQuotaApi resourceQuotaApi;
+
+    @Test
+    public void testCreate(){
+        ResourceQuota resourceQuota = new ResourceQuota();
+        resourceQuota.setNamespace("group21");
+        resourceQuota.setName("group21");
+        resourceQuota.setMaxCpuTotalLimit(new CpuQuantity(10));
+        resourceQuota.setMaxCpuTotalRequest(new CpuQuantity(10));
+        resourceQuota.setMaxMemoryTotalLimit(new StorageQuantity(500));
+        resourceQuota.setMaxMemoryTotalRequest(new StorageQuantity(500));
+    }
+
+    @Test
+    public void testUpdate(){
+        ResourceQuota resourceQuota = new ResourceQuota();
+    }
+
+    @Test
+    public void testDelete(){
+    }
+}

+ 118 - 0
src/test/java/nju/seec/SEECdemo/service/api/SecretApiImpl.java

@@ -0,0 +1,118 @@
+package nju.seec.SEECdemo.service.api;
+
+
+import io.kubernetes.client.apis.AppsV1Api;
+import io.kubernetes.client.apis.CoreV1Api;
+import nju.seec.SEECdemo.logic.api.k8s.SecretApi;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SecretApiImpl {
+
+    @Autowired
+    private AppsV1Api appsV1Api;
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    private SecretApi secretApi;
+
+    @Test
+    public void testCreateSecret() {
+
+        //Secret默认创建的Type为Opaque,base64编码格式的Secret,用来储存密码、密钥等。
+
+//        V1Secret v1Secret = new V1Secret();
+//        v1Secret.setApiVersion("v1");
+//        Map<String, byte[]> secretMap = new HashMap<>();
+//        String dockerConfigJson =
+//                "{\"auths\":{\"10.1.1.243:18082\":{\"username\":\"admin\",\"password\":\"admin123\",\"auth\":\"admin:admin123\"}}}";
+//        secretMap.put(".dockerconfigjson", dockerConfigJson.getBytes());
+//        V1ObjectMeta meta = new V1ObjectMeta();
+//        meta.setName("my-secret4");
+//        meta.setNamespace("mjj-test");
+//        v1Secret.setData(secretMap);
+//        v1Secret.setKind("Secret");
+//        v1Secret.setMetadata(meta);
+//        v1Secret.setType("123");
+//
+//        v1Secret.setType("kubernetes.io/dockerconfigjson");
+//
+//        try {
+//            V1Secret returnSecret = coreV1Api.createNamespacedSecret("mjj-test", v1Secret, "OK");
+//            Map<String, byte[]> dataMap = returnSecret.getData();
+//            for (Map.Entry<String, byte[]> entry : dataMap.entrySet()) {
+//                System.out.println(entry.getKey());
+//                System.out.println(new String(entry.getValue()));
+//            }
+//            System.out.println(returnSecret.getType());
+//        } catch (ApiException e) {
+//            //如果已经存在相同命名的Secret会抛出异常
+//            System.out.println(e.getResponseBody());
+//        }
+
+//        String namespace = "mjj-test";
+//        try {
+//            V1SecretList v1SecretList = coreV1Api.listNamespacedSecret(namespace, "OK", "", "",false, "", 200, "", 10, false);
+//            List<V1Secret> secretList = v1SecretList.getItems();
+//            for (V1Secret secret : secretList) {
+//
+//                Map<String, byte[]> data = secret.getData();
+//                for (Map.Entry<String, byte[]> entry : data.entrySet()) {
+//                    System.out.println(entry.getKey());
+//                    System.out.println(new String(entry.getValue()));
+//                }
+//            }
+//        } catch (ApiException e) {
+//            System.out.println(e.getResponseBody());
+//        }
+
+
+        //test createGenericSecret()
+/*        String namespace = "mjj-test";
+        String name = "secret";
+        Map<String, byte[]> data = new HashMap<>();
+        data.put("mysql", "123456".getBytes());
+        secretApi.createGenericSecret(namespace, name, data);*/
+
+        //test createPrivateRegistrySecret()
+        /*String namespace = "mjj-test";
+        String sever = "10.1.1.243:18082";
+        String user = "admin";
+        String password = "admin123";
+        secretApi.createPrivateRegistrySecret(namespace, sever, user, password);*/
+
+        //test getSecret()
+        /*String namespace = "mjj-test";
+        String name = "my-secret2";
+        ApiResult apiResult = secretApi.getSecret(namespace, name);
+        if (apiResult.getErrorCode() == 0) {
+            V1Secret secret = (V1Secret) apiResult.getValue();
+            Map<String, byte[]> map = secret.getData();
+            for (Map.Entry<String, byte[]> entry : map.entrySet()) {
+                System.out.println(entry.getKey());
+                System.out.println(new String(entry.getValue()));
+            }
+        } else {
+            System.out.println("获取失败");
+        }*/
+
+
+        //test delete a Secret
+        /*String name = "my-secret2";
+        String namespace = "mjj-test";*/
+        /*V1DeleteOptions body = new V1DeleteOptions();
+        try {
+            coreV1Api.deleteNamespacedSecret(name, namespace, body, "", 5, false, "");
+        } catch (ApiException e) {
+            System.out.println(e.getResponseBody());
+        }*/
+
+    }
+}

+ 48 - 0
src/test/java/nju/seec/SEECdemo/service/api/SecretApiTest.java

@@ -0,0 +1,48 @@
+package nju.seec.SEECdemo.service.api;
+
+import io.kubernetes.client.models.V1Secret;
+import nju.seec.SEECdemo.logic.api.k8s.SecretApi;
+import nju.seec.SEECdemo.logic.api.k8s.util.SecretTypeEnum;
+import nju.seec.SEECdemo.logic.api.k8s.vo.SecretVO;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * author: rale
+ * createdAt: 1/2/19
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SecretApiTest {
+
+    @Autowired
+    private SecretApi secretApi;
+
+    @Test
+    public void testGetSecret() {
+        SecretVO v1Secret = secretApi.getSecretByName("group24", "regcred");
+        System.out.println(v1Secret);
+    }
+
+    @Test
+    public void testGetSecretByType() {
+        List<SecretVO> v1Secret = secretApi.getSecretListByType("demo", SecretTypeEnum.REGISTRY);
+        v1Secret.stream().forEach((v1Secret1 -> {
+            v1Secret1.getData().values().forEach((stringEntry -> {
+                System.out.println(stringEntry);
+            }));
+        }));
+        System.out.println(v1Secret);
+    }
+
+    @Test
+    public void testCreate() {
+        secretApi.createPrivateRegistrySecret("demo2", "10.1.1.243:18082", "admin", "admin123");
+    }
+}

+ 56 - 0
src/test/java/nju/seec/SEECdemo/service/api/model/LabelSelectorTest.java

@@ -0,0 +1,56 @@
+package nju.seec.SEECdemo.service.api.model;
+
+import nju.seec.SEECdemo.logic.api.k8s.model.LabelSelector;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * author: rale
+ * createdAt: 1/7/19
+ */
+
+public class LabelSelectorTest {
+
+    @Test
+    public void testEquals() {
+        LabelSelector labelSelector = new LabelSelector();
+        labelSelector.addMatchExpression("key1", LabelSelector.Operator.IN, "value1");
+        String result = labelSelector.toJsonString();
+        Assert.assertEquals("key1=value1", result);
+    }
+
+    @Test
+    public void testNotEquals() {
+        LabelSelector labelSelector = new LabelSelector();
+        labelSelector.addMatchExpression("key1", LabelSelector.Operator.NOT_IN, "value1");
+        String result = labelSelector.toJsonString();
+        Assert.assertEquals("key1!=value1", result);
+    }
+
+    @Test
+    public void testIn() {
+        LabelSelector labelSelector = new LabelSelector();
+        labelSelector.addMatchExpression("key1", LabelSelector.Operator.NOT_IN, "value1", "value2");
+        String result = labelSelector.toJsonString();
+        Assert.assertEquals("key1 notin (value2,value1)", result);
+    }
+
+    @Test
+    public void testExists() {
+        LabelSelector labelSelector = new LabelSelector();
+        labelSelector.addMatchExpression("key1", LabelSelector.Operator.EXISTS, "value1");
+        String result = labelSelector.toJsonString();
+        Assert.assertEquals("key1", result);
+    }
+
+    @Test
+    public void testInAndExists() {
+        LabelSelector labelSelector = new LabelSelector();
+        labelSelector.addMatchExpression("key1", LabelSelector.Operator.EXISTS, "value1");
+    }
+}