Pārlūkot izejas kodu

In order to pull

马佳君 7 gadi atpakaļ
vecāks
revīzija
f27dfa38e0
40 mainītis faili ar 1221 papildinājumiem un 158 dzēšanām
  1. 4 1
      config.yml
  2. 23 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/ExecApi.java
  3. 11 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/LimitRangeApi.java
  4. 15 12
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/NamespaceApi.java
  5. 48 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/ResourceQuotaApi.java
  6. 19 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/SecretApi.java
  7. 51 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/exception/K8sApiException.java
  8. 87 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ExecApiImpl.java
  9. 76 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/IngressApiImpl.java
  10. 84 31
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/NamespaceApiImpl.java
  11. 212 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/ResourceQuotaImpl.java
  12. 2 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/SecretApiImpl.java
  13. 1 23
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/BeanAnnouncement.java
  14. 0 34
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/Constants.java
  15. 1 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ApiResult.java
  16. 2 1
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Container.java
  17. 1 1
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ContainerPort.java
  18. 32 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/CpuQuantity.java
  19. 2 1
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Deployment.java
  20. 8 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Namespace.java
  21. 73 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ResourceQuota.java
  22. 50 0
      src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/StorageQuantity.java
  23. 11 1
      src/main/java/nju/seec/SEECdemo/logic/service/ProjectService.java
  24. 0 36
      src/main/java/nju/seec/SEECdemo/logic/service/impl/ProjectServiceImpl.java
  25. 75 0
      src/main/java/nju/seec/SEECdemo/logic/service/impl/project/ProjectServiceImpl.java
  26. 1 0
      src/main/java/nju/seec/SEECdemo/logic/vo/ApplicationVO.java
  27. 3 2
      src/main/java/nju/seec/SEECdemo/logic/vo/ProjectVO.java
  28. 8 0
      src/main/java/nju/seec/SEECdemo/logic/vo/WebApplicationVO.java
  29. 1 5
      src/main/java/nju/seec/SEECdemo/logic/vo/projecttemplate/ProjectTemplate.java
  30. 34 0
      src/main/java/nju/seec/SEECdemo/util/BeanUtil.java
  31. 41 0
      src/main/java/nju/seec/SEECdemo/util/Constants.java
  32. 49 0
      src/main/java/nju/seec/SEECdemo/util/DateUtil.java
  33. 5 0
      src/main/java/nju/seec/SEECdemo/util/LoggerUtil.java
  34. 6 0
      src/main/java/nju/seec/SEECdemo/web/dto/ApplicationDTO.java
  35. 22 3
      src/main/java/nju/seec/SEECdemo/web/dto/PortDTO.java
  36. 1 5
      src/test/java/nju/seec/SEECdemo/service/api/DeploymentApiImplTest.java
  37. 64 0
      src/test/java/nju/seec/SEECdemo/service/api/ExecApiImplTest.java
  38. 27 0
      src/test/java/nju/seec/SEECdemo/service/api/IngressApiImplTest.java
  39. 32 2
      src/test/java/nju/seec/SEECdemo/service/api/NamespaceApiImplTest.java
  40. 39 0
      src/test/java/nju/seec/SEECdemo/service/api/ResourceQuotaApiImplTest.java

+ 4 - 1
config.yml

@@ -34,4 +34,7 @@ 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-mysql: jdbc:mysql://mysql.group25.svc.cluster.local:3306
+    public-mysql-username: root
+    public-mysql-password:

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

@@ -0,0 +1,23 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import java.util.List;
+
+public interface ExecApi {
+
+    /**
+     * 选择namespace下podName中的containerName的容器执行commands指令
+     * @param namespace
+     * @param podName
+     * @param containerName
+     * @param commands
+     */
+    void exec(String namespace, String podName, String containerName, List<String> commands);
+
+    /**
+     * 当且仅当pod下只有一个container时,该指令才会执行成功,否则会抛出异常
+     * @param namespace
+     * @param podName
+     * @param commands
+     */
+    void exec(String namespace, String podName, List<String> commands);
+}

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

@@ -0,0 +1,11 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+
+/**
+ * author: rale
+ * createdAt: 12/26/18
+ */
+public interface LimitRangeApi {
+
+}

+ 15 - 12
src/main/java/nju/seec/SEECdemo/logic/api/k8s/NamespaceApi.java

@@ -1,15 +1,11 @@
 package nju.seec.SEECdemo.logic.api.k8s;
 
-import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
 import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
 
 import java.util.List;
 import java.util.Map;
 
-/**
- * author: rale
- * createdAt: 12/22/18
- */
 public interface NamespaceApi {
 
     /**
@@ -17,14 +13,14 @@ public interface NamespaceApi {
      * @param namespace 命名空间名称需要唯一
      * @return
      */
-    ApiResult<Void> create(Namespace namespace);
+    void create(Namespace namespace) throws K8sApiException;
 
     /**
      * 异步新建一个命名空间
      * @param namespace
      * @return
      */
-    ApiResult<Void> createAsync(Namespace namespace);
+    void createAsync(Namespace namespace) throws K8sApiException;
 
 
     /**
@@ -32,27 +28,27 @@ public interface NamespaceApi {
      * @param name
      * @return
      */
-    ApiResult<Void> deleteByName(String name);
+    void deleteByName(String name) throws K8sApiException;
 
     /**
      * 获得名称为name的命名空间
      * @param name
      * @return
      */
-    ApiResult<Namespace> getByName(String name);
+    Namespace getByName(String name) throws K8sApiException;
 
     /**
      * 获得所有命名空间
      * @return
      */
-    ApiResult<List<Namespace>> getAll();
+    List<Namespace> getAll() throws K8sApiException;
 
     /**
      * 获取匹配labels标签的namespace
      * @param labels
      * @return
      */
-    ApiResult<List<Namespace>> getByLabels(Map<String, String> labels);
+    List<Namespace> getByLabels(Map<String, String> labels) throws K8sApiException;
 
     /**
      * 获取匹配了label标签的namespace
@@ -60,5 +56,12 @@ public interface NamespaceApi {
      * @param value
      * @return
      */
-    ApiResult<List<Namespace>> getByLabel(String key, String value);
+    List<Namespace> getByLabel(String key, String value) throws K8sApiException;
+
+    /**
+     * 判断该命名空间是否存在
+     * @param name
+     * @return
+     */
+    boolean exists(String name) throws K8sApiException;
 }

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

@@ -0,0 +1,48 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+import nju.seec.SEECdemo.logic.api.k8s.vo.ResourceQuota;
+
+public interface ResourceQuotaApi {
+
+//    /**
+//     * 新建资源限额,如果该命名空间下的限额已经存在,则更新限额
+//     * @param resourceQuota
+//     * @return
+//     */
+//    void create(ResourceQuota resourceQuota) throws K8sApiException;
+//
+//    /**
+//     * 更新资源限额
+//     * @param resourceQuota
+//     * @return
+//     */
+//    void update(ResourceQuota resourceQuota) throws K8sApiException;
+//
+//    /**
+//     * 删除namespace下名为name的资源限额
+//     * @param namespace
+//     * @param name
+//     * @return
+//     */
+//    void delete(String namespace, String name) throws K8sApiException;
+//
+//
+//    /**
+//     * 获取namespace下名为name的资源
+//     * @param namespace
+//     * @param name
+//     * @return
+//     * @throws K8sApiException
+//     */
+//    ResourceQuota getByName(String namespace, String name) throws K8sApiException;
+//
+//    /**
+//     * 当前的资源限额是否存在
+//     * @param namespace
+//     * @param name
+//     * @throws K8sApiException
+//     */
+//    boolean exist(String namespace, String name) throws K8sApiException;
+}

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

@@ -1,4 +1,23 @@
 package nju.seec.SEECdemo.logic.api.k8s;
 
+import java.util.Map;
+
 public interface SecretApi {
+
+    /**
+     * 创建通用密钥,如数据库的用户名,密码
+     * @param namespace
+     * @param name
+     * @param data
+     */
+    void createGenericSecret(String namespace, String name, Map<String, String> data);
+
+    /**
+     * 在namespace下生成私有仓库server的密钥
+     * 密钥名称将自动生成
+     * @param server
+     * @param user
+     * @param password
+     */
+    void createPrivateRegistrySecret(String namespace, String server, String user, String password);
 }

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

@@ -0,0 +1,51 @@
+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 Exception{
+
+    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 RESOURCE_QUOTA_NOT_EXIST = 402;
+
+    public static final int NAMESPACE_ALREADY_EXIST = 500;
+
+    public static final int ILLEGAL_PARAMETER = 100;
+
+    public static final int SYSTEM_ERROR = 200;
+
+    public static final K8sApiException K8s_SYSTEM_ERROR_EXCEPTION = new K8sApiException(SYSTEM_ERROR);
+    //系统故障
+    static {
+        codeMap.put(NAMESPACE_NOT_EXIST, "命名空间不存在");
+        codeMap.put(DEPLOYMENT_NOT_EXIST, "部署不存在");
+        codeMap.put(RESOURCE_QUOTA_NOT_EXIST, "资源限额不存在");
+
+        codeMap.put(NAMESPACE_ALREADY_EXIST, "命名空间已经存在");
+
+        codeMap.put(ILLEGAL_PARAMETER, "非法参数");
+    }
+    public K8sApiException(int code, String description){
+        this.code = code;
+        this.description = description;
+    }
+
+    public K8sApiException(int code) {
+        this.code = code;
+        this.description = codeMap.getOrDefault(code, "操作执行失败");
+    }
+
+
+}

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

@@ -0,0 +1,87 @@
+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 io.kubernetes.client.apis.CoreV1Api;
+import nju.seec.SEECdemo.logic.api.k8s.ExecApi;
+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;
+
+@Service
+public class ExecApiImpl implements ExecApi{
+
+    private static final Logger logger = LoggerUtil.getLogger(ExecApiImpl.class);
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+
+    @Override
+    public void exec(String namespace, String podName, String containerName, List<String> commands) {
+
+    }
+
+    @Override
+    public void exec(String namespace, String podName, List<String> commands) {
+        Exec exec = new Exec();
+
+        boolean tty = System.console() != null;
+        try {
+            final Process proc =
+                    exec.exec(
+                            namespace,
+                            podName,
+                            commands.isEmpty()
+                                    ? new String[] {"sh"}
+                                    : commands.toArray(new String[commands.size()]),
+                            true,
+                            tty);
+
+            Thread in =
+                    new Thread(
+                            new Runnable() {
+                                public void run() {
+                                    try {
+                                        ByteStreams.copy(System.in, proc.getOutputStream());
+                                    } catch (IOException ex) {
+                                        ex.printStackTrace();
+                                    }
+                                }
+                            });
+            in.start();
+
+            Thread out =
+                    new Thread(
+                            new Runnable() {
+                                public void run() {
+                                    try {
+                                        ByteStreams.copy(proc.getInputStream(), System.out);
+                                    } catch (IOException ex) {
+                                        ex.printStackTrace();
+                                    }
+                                }
+                            });
+            out.start();
+
+            proc.waitFor();
+
+            // wait for any last output; no need to wait for input thread
+            out.join();
+
+            proc.destroy();
+
+            System.exit(proc.exitValue());
+        } catch (ApiException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}

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

@@ -0,0 +1,76 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.apis.AppsV1beta1Api;
+import io.kubernetes.client.apis.CoreV1Api;
+import io.kubernetes.client.apis.ExtensionsV1beta1Api;
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.models.*;
+import io.kubernetes.client.proto.V1beta1Apps;
+import io.kubernetes.client.proto.V1beta1Extensions;
+import nju.seec.SEECdemo.logic.api.k8s.IngressApi;
+import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+import nju.seec.SEECdemo.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+@Service
+public class IngressApiImpl implements IngressApi{
+    private static final Logger logger = LoggerUtil.getLogger(IngressApi.class);
+    @Autowired
+    private ExtensionsV1beta1Api extensionsV1beta1Api;
+
+    //@todo fix bug
+    @Override
+    public ApiResult<String> create(String namespace, String name, String path, String serviceName, int port) {
+        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
+                .withName(name)
+                .withNamespace(namespace)
+                .build();
+
+        V1beta1HTTPIngressPath v1beta1HTTPIngressPath = new V1beta1HTTPIngressPathBuilder()
+                .withPath(path)
+                .withBackend(new V1beta1IngressBackendBuilder()
+                        .withServiceName(serviceName)
+                        .withServicePort(new IntOrString(port))
+                        .build())
+                .build();
+        V1beta1HTTPIngressRuleValue v1beta1HTTPIngressRuleValue = new V1beta1HTTPIngressRuleValueBuilder()
+                .addToPaths(v1beta1HTTPIngressPath)
+                 .build();
+        V1beta1IngressRule v1beta1IngressRule = new V1beta1IngressRuleBuilder()
+                .withHost("deernowl.cn")
+                .withHttp(v1beta1HTTPIngressRuleValue)
+                .build();
+        V1beta1IngressSpec v1beta1IngressSpec = new V1beta1IngressSpecBuilder()
+                .withRules(v1beta1IngressRule)
+                .build();
+        V1beta1Ingress v1beta1Ingress = new V1beta1IngressBuilder()
+                .withApiVersion("extensions/v1beta1")
+                .withKind("Ingress")
+                .withMetadata(v1ObjectMeta)
+                .withSpec(v1beta1IngressSpec)
+                .build();
+
+        try {
+            extensionsV1beta1Api.createNamespacedIngress(namespace, v1beta1Ingress, "pretty");
+        } catch (ApiException e) {
+            LoggerUtil.error(logger, e, "ingress 创建失败,v1BetaIngress={}, response={}", v1beta1Ingress, e.getResponseBody());
+            ApiResult<String> apiResult = new ApiResult<>();
+            apiResult.setSuccess(false);
+            apiResult.setErrorCode(e.getCode());
+            apiResult.setErrorDescription(e.getMessage());
+            return apiResult;
+        }
+        ApiResult<String> result = new ApiResult<>();
+        result.setSuccess(true);
+        result.setValue("deernowl.cn" + path);
+        return result;
+    }
+
+}

+ 84 - 31
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/NamespaceApiImpl.java

@@ -7,9 +7,10 @@ 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.exception.K8sApiException;
 import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
 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;
@@ -18,91 +19,120 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import static nju.seec.SEECdemo.logic.api.k8s.util.Constants.PRETTY_FORMAT;
+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;
 
-/**
- * author: rale
- * createdAt: 12/22/18
- */
 @Service
 public class NamespaceApiImpl implements NamespaceApi{
 
     private Logger logger = LoggerUtil.getLogger(NamespaceApi.class);
 
-    private static final String FOREGROUND_PROPAGATION_POLICY = "Foreground";
     @Autowired
     private CoreV1Api coreV1Api;
 
     /**
-     * @todo 此处没有明确项目的LimitRange
      * @param namespace
      * @return
      * @see NamespaceApi#create(Namespace)
      */
     @Override
-    public ApiResult<Void> create(Namespace namespace) {
+    public void create(Namespace namespace) throws K8sApiException{
+        if (exists(namespace.getName())) {
+            throw new K8sApiException(K8sApiException.NAMESPACE_ALREADY_EXIST);
+        }
         V1Namespace v1Namespace = namespace.toV1Namespace();
         try {
             coreV1Api.createNamespace(v1Namespace, "true");
         } catch (ApiException e) {
             LoggerUtil.error(logger, e, "Namespace创建失败,namespace={}, v1Namespace={}", namespace, v1Namespace);
-            return new ApiResult<>(e.getCode(), e.getMessage());
+            throw new K8sApiException(K8sApiException.SYSTEM_ERROR);
         }
-        ApiResult<Void> result = new ApiResult<>();
-        result.setSuccess(true);
-        return result;
     }
 
     @Override
-    public ApiResult<Void> createAsync(Namespace namespace) {
-        return null;
+    public void createAsync(Namespace namespace) throws K8sApiException{
     }
 
     @Override
-    public ApiResult<Void> deleteByName(String name) {
+    public void deleteByName(String name) throws K8sApiException{
+        if (!exists(name)) {
+            throw new K8sApiException(K8sApiException.NAMESPACE_NOT_EXIST);
+        }
         try {
             V1DeleteOptions v1DeleteOptions = new V1DeleteOptionsBuilder()
                     .withApiVersion(Namespace.VERSION)
                     .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
                     .build();
-            coreV1Api.deleteNamespace(name, v1DeleteOptions, PRETTY_FORMAT,
-                    null, null, FOREGROUND_PROPAGATION_POLICY);
+            coreV1Api.deleteNamespace(
+                    name,
+                    v1DeleteOptions,
+                    PRETTY_FORMAT,
+                    null,
+                    null,
+                    FOREGROUND_PROPAGATION_POLICY);
         } catch (ApiException e) {
             LoggerUtil.error(logger, e, "Namespace删除失败,name={}, response={}", name , e.getResponseBody());
-            return new ApiResult<>(e.getCode(), e.getMessage());
+            throw K8sApiException.K8s_SYSTEM_ERROR_EXCEPTION;
         } catch (Exception e) {
+            //k8s 由swagger导致的bug
             LoggerUtil.warn(logger, e, "k8s swagger bug");
-            return new ApiResult<>(null);
         }
-        return new ApiResult<>(null);
     }
 
     @Override
-    public ApiResult<Namespace> getByName(String name) {
+    public Namespace getByName(String name) throws K8sApiException{
+        if (exists(name)){
+            try {
+                V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
+                        PRETTY_FORMAT,
+                        null,
+                        METADATA_NAME_SELECTOR + name,
+                        null,
+                        null,
+                        null,
+                        null,
+                        null,
+                        null);
+                List<Namespace> result = v1NamespaceList.getItems().stream().limit(1).map(Namespace::new).collect(Collectors.toList());
+                return result.get(0);
+            }catch (ApiException e) {
+                LoggerUtil.error(logger, e, "查询Namespace失败,name={}, response={}", name, e.getResponseBody());
+                throw new K8sApiException(K8sApiException.SYSTEM_ERROR);
+            }
+        }
         return null;
     }
 
     @Override
-    public ApiResult<List<Namespace>> getAll() {
+    public List<Namespace> getAll() throws K8sApiException{
         try {
-            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(PRETTY_FORMAT, null, null,
-                    true, null, null, null, null, null);
+            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 new ApiResult<>(namespaces);
+            return namespaces;
         } catch (ApiException e) {
             LoggerUtil.error(logger, e, "获取全部Namespace异常, response={}", e.getResponseBody());
-            return new ApiResult<>(e.getCode(), e.getMessage());
+            throw new K8sApiException(K8sApiException.SYSTEM_ERROR);
         }
     }
 
     @Override
-    public ApiResult<List<Namespace>> getByLabels(Map<String, String> labels) {
+    public List<Namespace> getByLabels(Map<String, String> labels) {
         return null;
     }
 
     @Override
-    public ApiResult<List<Namespace>> getByLabel(String key, String value) {
+    public List<Namespace> getByLabel(String key, String value) throws K8sApiException{
         try {
             String labelSelector = buildLabelSelector(key, value);
             V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(
@@ -117,13 +147,34 @@ public class NamespaceApiImpl implements NamespaceApi{
                     null);
             List<Namespace> namespaces =  v1NamespaceList.getItems().stream().map(Namespace::new).collect(Collectors.toList());
             LoggerUtil.info(logger,"result={}", namespaces);
-            return new ApiResult<>(namespaces);
+            return namespaces;
         } catch (ApiException e) {
             LoggerUtil.error(logger, e, "根据标签获取Namespace异常, key={}, value={}, response={}", key, value, e.getResponseBody());
-            return new ApiResult<>(e.getCode(), e.getMessage());
+            throw new K8sApiException(K8sApiException.SYSTEM_ERROR);
+        }
+    }
+
+    @Override
+    public boolean exists(String name) throws K8sApiException {
+        if (StringUtils.isBlank(name)) throw new K8sApiException(K8sApiException.ILLEGAL_PARAMETER, "名称不能为空");
+        try {
+            V1NamespaceList v1NamespaceList = coreV1Api.listNamespace(PRETTY_FORMAT,
+                    null,
+                    METADATA_NAME_SELECTOR + name,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+            return v1NamespaceList.getItems().size() > 0;
+        }catch (ApiException e) {
+            LoggerUtil.error(logger, e, "查询命名空间失败, name={}, response={}", name, e.getResponseBody());
+            return false;
         }
     }
 
+
     private String buildLabelSelector(String key, String value) {
         return key + "=" + value;
     }
@@ -138,4 +189,6 @@ public class NamespaceApiImpl implements NamespaceApi{
         }
         return result.toString();
     }
+
+
 }

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

@@ -0,0 +1,212 @@
+package nju.seec.SEECdemo.logic.api.k8s.impl;
+
+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.ResourceQuotaApi;
+//import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+//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.vo.ResourceQuota;
+//import nju.seec.SEECdemo.util.LoggerUtil;
+//import org.slf4j.Logger;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.stereotype.Service;
+//import org.springframework.util.CollectionUtils;
+//
+//
+//import java.awt.*;
+//import java.util.List;
+//import java.util.stream.Collectors;
+//
+//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;
+//
+//    /**
+//     * @see ResourceQuotaApi#create(ResourceQuota)
+//     * @param resourceQuota
+//     * @return
+//     */
+//    @Override
+//    public void create(ResourceQuota resourceQuota) throws K8sApiException{
+//        ApiResult<Namespace> namespaceRequest = namespaceApi.getByName(resourceQuota.getName());
+//        //Namespace不存在
+//        if (namespaceRequest.getErrorCode() == ApiResult.NOT_EXIST) {
+//            return new ApiResult<>( ApiResult.NOT_EXIST, "该命名空间不存在");
+//        }
+//        ApiResult<Boolean> resourceQuotaExistRequest = this.exist(resourceQuota.getNamespace(), resourceQuota.getName());
+//        if (!resourceQuotaExistRequest.isSuccess()) {
+//            return new ApiResult<>(ApiResult.SYSTEM_ERROR, "resource quota查询失败");
+//        }
+//
+//        //该资源限额已经存在,直接更新资源限额
+//        if (resourceQuotaExistRequest.getValue()) {
+//            return update(resourceQuota);
+//        }
+//
+//        try {
+//            coreV1Api.createNamespacedResourceQuota(
+//                    resourceQuota.getNamespace(),
+//                    resourceQuota.toV1ResourceQuota(),
+//                    PRETTY_FORMAT);
+//        } catch (ApiException e) {
+//            LoggerUtil.error(logger, e, "创建resource quota失败, resourceQuota={}, response={}",
+//                    resourceQuota, e.getResponseBody());
+//            return new ApiResult<>(e.getCode(), e.getMessage());
+//        }
+//        return new ApiResult<>(null);
+//    }
+//
+//    /**
+//     * @see ResourceQuotaApi#update(ResourceQuota)
+//     * @param resourceQuota
+//     * @return
+//     */
+//    @Override
+//    public ApiResult<Void> update(ResourceQuota resourceQuota) {
+//        ApiResult<Namespace> namespaceRequest = namespaceApi.getByName(resourceQuota.getName());
+//        //Namespace不存在
+//        if (namespaceRequest.getErrorCode() == ApiResult.NOT_EXIST) {
+//            return new ApiResult<>( ApiResult.NOT_EXIST, "该命名空间不存在");
+//        }
+//
+//        ApiResult<Boolean> resourceQuotaExistRequest = this.exist(resourceQuota.getNamespace(), resourceQuota.getName());
+//        if (!resourceQuotaExistRequest.isSuccess()) {
+//            return new ApiResult<>(ApiResult.SYSTEM_ERROR, "resource quota查询失败");
+//        }
+//        if (!resourceQuotaExistRequest.getValue()) {
+//            return new ApiResult<>(ApiResult.NOT_EXIST, "名称为" + resourceQuota.getName() + "的资源限额不存在");
+//        }
+//
+//        try {
+//            coreV1Api.patchNamespacedResourceQuota(
+//                    resourceQuota.getNamespace(),
+//                    resourceQuota.getNamespace(),
+//                    resourceQuota.toV1ResourceQuota(),
+//                    PRETTY_FORMAT);
+//        } catch (ApiException e) {
+//            LoggerUtil.error(logger, e, "更新Resource Quota失败, resourceQuota={}, response={}", resourceQuota, e.getResponseBody());
+//            return new ApiResult<>(e.getCode(), e.getMessage());
+//        }
+//        return new ApiResult<>(null);
+//    }
+//
+//    @Override
+//    public ApiResult<Void> delete(String namespace, String name) {
+//        ApiResult<Namespace> namespaceRequest = namespaceApi.getByName(namespace);
+//        //系统错误
+//        if (!namespaceRequest.isSuccess()) {
+//            return new ApiResult<>(namespaceRequest.getErrorCode(), namespaceRequest.getErrorDescription());
+//        }
+//        //Namespace不存在
+//        if (namespaceRequest.getErrorCode() == ApiResult.NOT_EXIST) {
+//            return new ApiResult<>( ApiResult.NOT_EXIST, "该命名空间不存在");
+//        }
+//
+//
+//        ApiResult<Boolean> resourceQuotaExistRequest = this.exist(namespace, name);
+//        //系统错误
+//        if (!resourceQuotaExistRequest.isSuccess()) {
+//            return new ApiResult<>(ApiResult.SYSTEM_ERROR, "resource quota查询失败");
+//        }
+//        //该资源限额不存在
+//        if (!resourceQuotaExistRequest.getValue()) {
+//            return new ApiResult<>(ApiResult.NOT_EXIST, "该资源限额不存在");
+//        }
+//
+//        V1DeleteOptions deleteOptions = new V1DeleteOptionsBuilder()
+//                .withApiVersion(ResourceQuota.API_VERSION)
+//                .withApiVersion(ResourceQuota.KIND)
+//                .withPropagationPolicy(FOREGROUND_PROPAGATION_POLICY)
+//                .build();
+//
+//        try {
+//            coreV1Api.deleteNamespacedResourceQuota(
+//                    name,
+//                    namespace,
+//                    deleteOptions,
+//                    PRETTY_FORMAT,
+//                    null,
+//                    null,
+//                    FOREGROUND_PROPAGATION_POLICY);
+//        } catch (ApiException e) {
+//            LoggerUtil.error(logger, e, "删除resource quota异常,namespace={}, name={}, response={}",
+//                    namespace, name, e.getResponseBody());
+//        }
+//        return new ApiResult<>(null);
+//    }
+//
+//    @Override
+//    public ResourceQuota getByName(String namespace, String name) throws K8sApiException {
+//        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) {
+//            LoggerUtil.error(logger, e, "查找resource quota失败, namespace={}, response={}", namespace, e.getResponseBody());
+//            return false;
+//        }
+//    }
+//
+//    /**
+//     * @see ResourceQuotaApi#getByNamespace(String)
+//     * @param namespace
+//     * @return
+//     */
+//    @Override
+//    public ApiResult<ResourceQuota> getByNamespace(String namespace) {
+//        try {
+//            V1ResourceQuotaList resourceQuotaList = coreV1Api.listNamespacedResourceQuota(namespace,
+//                    PRETTY_FORMAT,
+//                    null,
+//                    null,
+//                    null,
+//                    null,
+//                    null,
+//                    null,
+//                    null,
+//                    null);
+//            if (resourceQuotaList != null && !CollectionUtils.isEmpty(resourceQuotaList.getItems())) {
+//                List<ResourceQuota> result = resourceQuotaList.getItems().stream().map(ResourceQuota::new).collect(Collectors.toList());
+//                return new ApiResult<>(result.get(0));
+//            }
+//        } catch (ApiException e) {
+//            LoggerUtil.error(logger, e, "查找Resource Quota失败, namespace={}, response={}", namespace, e.getResponseBody());
+//            return new ApiResult<>(e.getCode(), e.getMessage());
+//        }
+//        return new ApiResult<>(null);
+//    }
+//
+
+}

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

@@ -1,4 +1,6 @@
 package nju.seec.SEECdemo.logic.api.k8s.impl;
 
+
 public class SecretApiImpl {
+
 }

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

@@ -6,8 +6,6 @@ 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;
@@ -51,25 +49,5 @@ public class BeanAnnouncement {
         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;
-    }
+
 }

+ 0 - 34
src/main/java/nju/seec/SEECdemo/logic/api/k8s/util/Constants.java

@@ -1,34 +0,0 @@
-package nju.seec.SEECdemo.logic.api.k8s.util;
-
-public abstract class Constants {
-
-	//标签域名前缀
-	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 NAME_PATTERN = "^[0-9a-zA-Z-]{8,}$";
-
-	public static final String PRETTY_FORMAT = "false";
-
-	static final String NAMESPACE_API_VERSION = "v1";
-
-}

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

@@ -25,6 +25,7 @@ public class ApiResult<T> {
 
     public static final int NOT_EXIST = 800; //资源不存在
 
+    public static final int SYSTEM_ERROR = 100; //系统错误
     public ApiResult(T t){
         this.value = t;
         this.success = true;

+ 2 - 1
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Container.java

@@ -13,7 +13,8 @@ import javax.validation.constraints.Pattern;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static nju.seec.SEECdemo.logic.api.k8s.util.Constants.NAME_PATTERN;
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
+
 
 /**
  * author: rale

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

@@ -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.util.Constants.NAME_PATTERN;
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
 
 /**
  * author: rale

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

@@ -0,0 +1,32 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.custom.Quantity;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+public class CpuQuantity {
+
+    private BigDecimal quantity;
+
+    public CpuQuantity(int quantity){
+        //转化成百分比
+        this.quantity = BigDecimal.valueOf(quantity * 1.0 / 100);
+    }
+
+    public CpuQuantity(Quantity quantity) {
+        this.quantity = quantity.getNumber();
+    }
+
+    public Quantity toQuantity(){
+        return new Quantity(quantity, Quantity.Format.DECIMAL_SI);
+    }
+
+    public int getQuantity(){
+        return quantity.intValue();
+    }
+
+}

+ 2 - 1
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Deployment.java

@@ -10,7 +10,8 @@ import javax.validation.constraints.NotNull;
 import java.util.*;
 import java.util.stream.Collectors;
 
-import static nju.seec.SEECdemo.logic.api.k8s.util.Constants.APPLICATION_LABEL;
+import static nju.seec.SEECdemo.util.Constants.APPLICATION_LABEL;
+
 
 /**
  * author: rale

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

@@ -66,4 +66,12 @@ public class Namespace {
         return v1Namespace;
 
     }
+
+    public void addToLabel(String key, String value) {
+        labels.put(key, value);
+    }
+
+    public void addToAnnotation(String key, String value){
+        annotations.put(key, value);
+    }
 }

+ 73 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ResourceQuota.java

@@ -0,0 +1,73 @@
+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 java.util.HashMap;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+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;
+
+    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();
+
+    }
+
+    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, maxCpuTotalRequest.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/vo/StorageQuantity.java

@@ -0,0 +1,50 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.custom.Quantity;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+public class StorageQuantity {
+
+    private BigDecimal quantity;
+
+    //存储的默认单位为MB
+    private Unit unit = Unit.MB;
+
+    //存储单位
+    public enum Unit{
+        KB("Ki"),
+        MB("Mi"),
+        GB("Gi");
+
+        private String code;
+
+        Unit(String code) {
+            this.code = code;
+        }
+    }
+
+    public StorageQuantity(int quantity){
+        this.quantity = BigDecimal.valueOf(quantity);
+    }
+
+    public StorageQuantity(int quantity, Unit unit) {
+        this.quantity = BigDecimal.valueOf(quantity);
+        this.unit = unit;
+    }
+
+    public StorageQuantity(Quantity quantity) {
+        this.quantity = quantity.getNumber();
+        //@todo 检验到底是啥单位
+    }
+
+    public Quantity toQuantity(){
+        return new Quantity(quantity, Quantity.Format.DECIMAL_EXPONENT);
+    }
+
+
+}

+ 11 - 1
src/main/java/nju/seec/SEECdemo/logic/service/ProjectService.java

@@ -2,6 +2,7 @@ package nju.seec.SEECdemo.logic.service;
 
 import nju.seec.SEECdemo.logic.vo.ProjectVO;
 
+import java.time.LocalDate;
 import java.util.Date;
 
 public interface ProjectService {
@@ -17,8 +18,17 @@ public interface ProjectService {
      * @return 项目创建详情
      * @exception nju.seec.SEECdemo.logic.exception.ServiceException
      */
-     ProjectVO createSeecIIProject(String projectName, String description, Date startsAt, Date endsAt);
+     ProjectVO createSeecIIProject(String projectName, String description, LocalDate startsAt, LocalDate endsAt);
 
+    /**
+     * 异步创建软工二项目
+     * @param projectName
+     * @param description
+     * @param startsAt
+     * @param endsAt
+     * @return
+     */
+     ProjectVO createSeecIIProjectAsync(String projectName, String description, LocalDate startsAt, LocalDate endsAt);
     /**
      * 删除项目
      * 如果项目底下仍有尚在运行的应用,则不会删除

+ 0 - 36
src/main/java/nju/seec/SEECdemo/logic/service/impl/ProjectServiceImpl.java

@@ -1,36 +0,0 @@
-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 nju.seec.SEECdemo.logic.service.ProjectService;
-import nju.seec.SEECdemo.logic.vo.ProjectVO;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-@Service
-public class ProjectServiceImpl implements ProjectService{
-    @Autowired
-    private CoreV1Api api;
-
-
-    @Override
-    public ProjectVO createSeecIIProject(String projectName, String description, Date startsAt, Date endsAt) {
-        return null;
-    }
-
-    @Override
-    public boolean deleteProject(long projectId) {
-        return false;
-    }
-
-    @Override
-    public boolean deleteProjectForce(long projectId) {
-        return false;
-    }
-}

+ 75 - 0
src/main/java/nju/seec/SEECdemo/logic/service/impl/project/ProjectServiceImpl.java

@@ -0,0 +1,75 @@
+package nju.seec.SEECdemo.logic.service.impl.project;
+
+import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
+import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
+import nju.seec.SEECdemo.logic.service.ProjectService;
+import nju.seec.SEECdemo.logic.vo.ProjectVO;
+import nju.seec.SEECdemo.logic.vo.projecttemplate.ProjectTemplate;
+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.time.LocalDate;
+
+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 ProjectTemplate seecIITemplate;
+
+    @Override
+    public ProjectVO createSeecIIProject(String projectName, String description, LocalDate startsAt, LocalDate endsAt) {
+        try {
+            if (namespaceApi.exists(projectName)){
+                System.out.println("该项目已经存在");
+                return null;
+            }
+
+            String type = seecIITemplate.getName();
+            Namespace namespace = buildNamespace(projectName, type, description, startsAt, endsAt);
+            namespaceApi.create(namespace);
+
+
+        } catch (K8sApiException e) {
+
+        }
+
+
+        return null;
+    }
+
+    @Override
+    public ProjectVO createSeecIIProjectAsync(String projectName, String description, LocalDate startsAt, LocalDate endsAt) {
+        return null;
+    }
+
+    @Override
+    public boolean deleteProject(long projectId) {
+        return false;
+    }
+
+    @Override
+    public boolean deleteProjectForce(long projectId) {
+        return false;
+    }
+
+    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;
+    }
+}

+ 1 - 0
src/main/java/nju/seec/SEECdemo/logic/vo/ApplicationVO.java

@@ -4,6 +4,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.util.List;
+import java.util.Map;
 
 @Data
 @EqualsAndHashCode

+ 3 - 2
src/main/java/nju/seec/SEECdemo/logic/vo/ProjectVO.java

@@ -4,13 +4,12 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.util.Date;
+import java.util.Map;
 
 @Data
 @EqualsAndHashCode
 public class ProjectVO {
 
-    private Long id;
-
     private String name;
 
     private String description;
@@ -18,4 +17,6 @@ public class ProjectVO {
     private Date startsAt;
 
     private Date endsAt;
+
+    private Map<String, String> extra;
 }

+ 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 {
+}

+ 1 - 5
src/main/java/nju/seec/SEECdemo/logic/vo/projecttemplate/ProjectTemplate.java

@@ -4,10 +4,6 @@ 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;
-
 @Data
 @EqualsAndHashCode
 public class ProjectTemplate {
@@ -20,5 +16,5 @@ public class ProjectTemplate {
 
     private ResourceConfig defaultResourceConfig;
 
-    private List<Function> operations = new ArrayList<>();
 }
+

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

@@ -0,0 +1,34 @@
+package nju.seec.SEECdemo.util;
+
+import nju.seec.SEECdemo.data.entity.resource.ResourceConfig;
+import nju.seec.SEECdemo.logic.vo.projecttemplate.ProjectTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+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(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;
+    }
+}

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

@@ -0,0 +1,41 @@
+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-at";
+
+    public static final String ENDS_AT_LABEL = LABEL_PREFIX + "/end-at";
+
+    public static final String PRETTY_FORMAT = "false";
+
+    //前台清除策略
+    public static final String FOREGROUND_PROPAGATION_POLICY = "Foreground";
+
+    public static final String METADATA_NAME_SELECTOR = "metadata.name=";
+
+}

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

@@ -0,0 +1,49 @@
+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, 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);
     }

+ 6 - 0
src/main/java/nju/seec/SEECdemo/web/dto/ApplicationDTO.java

@@ -17,9 +17,15 @@ public class ApplicationDTO {
 
     private List<ContainerDTO> containers;
 
+    @Range(min = 1, max = 100)
     private int cpuRequest;
 
+    /**
+     * 存储的默认单位为M
+     */
+    @Range(min = 100)
     private int memoryRequest;
 
+    @Range(min = 0)
     private int storageRequest;
 }

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

@@ -1,12 +1,31 @@
 package nju.seec.SEECdemo.web.dto;
 
-public class PortDTO {
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.Pattern;
 
-    private String name;
+import static nju.seec.SEECdemo.util.Constants.NAME_PATTERN;
+
+@Data
+public class PortDTO {
 
+    /**
+     * 容器暴露的端口号
+     */
+    @Range(min = 1, max = 65535)
     private int port;
 
-    private int targetPort;
+    /**
+     * 是否需要暴露在外网访问
+     * 暴露则需要分配外部访问的IP
+     */
+    private boolean expose;
 
+    /**
+     * 协议
+     */
+    @Pattern(regexp = "^(TCP|UDP|HTTP)$")
+    private String protocol;
 
 }

+ 1 - 5
src/test/java/nju/seec/SEECdemo/service/api/DeploymentApiImplTest.java

@@ -16,10 +16,6 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
-/**
- * author: rale
- * createdAt: 12/24/18
- */
 @RunWith(SpringRunner.class)
 @SpringBootTest
 public class DeploymentApiImplTest {
@@ -31,7 +27,7 @@ public class DeploymentApiImplTest {
     public void testCreate() {
         Deployment deployment = new Deployment();
         deployment.setName("test");
-        deployment.setNamespace("group24");
+        deployment.setNamespace("group21");
         deployment.setReplicas(1);
 
         ServicePort servicePort = new ServicePort();

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

@@ -0,0 +1,64 @@
+package nju.seec.SEECdemo.service.api;
+
+import com.google.common.io.ByteStreams;
+import io.kubernetes.client.ApiException;
+import io.kubernetes.client.Attach;
+import io.kubernetes.client.Exec;
+import nju.seec.SEECdemo.logic.api.k8s.ExecApi;
+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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.Collections;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ExecApiImplTest {
+
+    @Autowired
+    private ExecApi execApi;
+
+    @Test
+    public void testExec() throws IOException, ApiException, InterruptedException {
+        Exec exec = new Exec();
+        boolean tty = System.console() != null;
+        // final Process proc = exec.exec("default", "nginx-4217019353-k5sn9", new String[]
+        //   {"sh", "-c", "echo foo"}, true, tty);
+        final Process proc =
+                exec.exec(
+                        "group24",
+                        "mysql-7b688cf578-l7pxs",
+                        new String[]{"echo hello"},
+                        false,
+                        tty);
+
+
+        Thread out =
+                new Thread(
+                        new Runnable() {
+                            public void run() {
+                                try {
+                                    ByteStreams.copy(proc.getInputStream(), System.out);
+                                } catch (IOException ex) {
+                                    ex.printStackTrace();
+                                }
+                            }
+                        });
+        out.start();
+
+        proc.waitFor();
+
+        // wait for any last output; no need to wait for input thread
+        out.join();
+
+        proc.destroy();
+
+        System.exit(proc.exitValue());
+    }
+}

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

@@ -0,0 +1,27 @@
+package nju.seec.SEECdemo.service.api;
+
+import io.kubernetes.client.models.V1Secret;
+import io.kubernetes.client.models.V1SecretBuilder;
+import nju.seec.SEECdemo.logic.api.k8s.IngressApi;
+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;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class IngressApiImplTest {
+
+    @Autowired
+    private IngressApi ingressApi;
+
+    @Test
+    public void testCreate(){
+        ingressApi.create("group24", "test", "/", "test", 80);
+    }
+}

+ 32 - 2
src/test/java/nju/seec/SEECdemo/service/api/NamespaceApiImplTest.java

@@ -1,6 +1,7 @@
 package nju.seec.SEECdemo.service.api;
 
 import nju.seec.SEECdemo.logic.api.k8s.NamespaceApi;
+import nju.seec.SEECdemo.logic.api.k8s.exception.K8sApiException;
 import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -8,6 +9,9 @@ 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.deleteByName("group21");
+    }
+
+    @Test
+    public void testGetAll() throws K8sApiException {
+        namespaceApi.getAll();
+    }
+
+    @Test
+    public void testGetByLabel() throws K8sApiException {
+        namespaceApi.getByLabel("name", "group24");
+    }
+
+    @Test
+    public void testGetByLabels() throws K8sApiException {
+        Map<String, String> map = new HashMap<>();
+        map.put("test1", "test1");
+        map.put("test2", "test2");
+        namespaceApi.getByLabels(map);
+    }
 }

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

@@ -0,0 +1,39 @@
+package nju.seec.SEECdemo.service.api;
+
+import nju.seec.SEECdemo.logic.api.k8s.ResourceQuotaApi;
+import nju.seec.SEECdemo.logic.api.k8s.vo.CpuQuantity;
+import nju.seec.SEECdemo.logic.api.k8s.vo.ResourceQuota;
+import nju.seec.SEECdemo.logic.api.k8s.vo.StorageQuantity;
+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 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(){
+    }
+}