Преглед изворни кода

DeploymentApi的开发和简单测试

raledong пре 7 година
родитељ
комит
e7be3d68d3

+ 5 - 5
pom.xml

@@ -140,11 +140,11 @@
 			<scope>compile</scope>
 		</dependency>
 
-		<dependency>
-			<groupId>io.fabric8</groupId>
-			<artifactId>kubernetes-client</artifactId>
-			<version>3.1.12</version>
-		</dependency>
+		<!--<dependency>-->
+			<!--<groupId>io.fabric8</groupId>-->
+			<!--<artifactId>kubernetes-client</artifactId>-->
+			<!--<version>3.1.12</version>-->
+		<!--</dependency>-->
 	</dependencies>
 
 	<build>

+ 15 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/DeploymentApi.java

@@ -0,0 +1,15 @@
+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;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+public interface DeploymentApi {
+
+    ApiResult<Void> createSync(Deployment deployment);
+
+    ApiResult<Void> createAsync();
+}

+ 12 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/IngressApi.java

@@ -0,0 +1,12 @@
+package nju.seec.SEECdemo.logic.api.k8s;
+
+import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
+
+/**
+ * author: rale
+ * createdAt: 12/24/18
+ */
+public interface IngressApi {
+
+    ApiResult<String> create(String namespace, String name, String path, String serviceName, int port);
+}

+ 5 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/NamespaceApi.java

@@ -10,5 +10,10 @@ import nju.seec.SEECdemo.logic.api.k8s.vo.Namespace;
  */
 public interface NamespaceApi {
 
+    /**
+     * 新建一个命名空间
+     * @param namespace 命名空间名称需要唯一
+     * @return
+     */
     ApiResult<Void> create(Namespace namespace);
 }

+ 69 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/impl/DeploymentApiImpl.java

@@ -0,0 +1,69 @@
+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 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.util.LoggerUtil;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Service
+public class DeploymentApiImpl implements DeploymentApi{
+
+    private static final Logger logger = LoggerUtil.getLogger(DeploymentApi.class);
+    @Autowired
+    private AppsV1Api appsV1Api;
+
+    @Autowired
+    private CoreV1Api coreV1Api;
+
+    @Autowired
+    private NamespaceApi namespaceApi;
+
+    @Override
+    public ApiResult<Void> createSync(Deployment deployment) {
+        //@todo 判断命名空间是否存在
+        try {
+            V1Deployment v1Deployment = deployment.toV1Deployment();
+            appsV1Api.createNamespacedDeployment(deployment.getNamespace(),v1Deployment, "true");
+        } 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;
+        }
+
+        try {
+            V1Service v1Service = deployment.toV1Service();
+            coreV1Api.createNamespacedService(deployment.getNamespace(), v1Service, "true");
+        } 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;
+        }
+        ApiResult<Void> result = new ApiResult<>();
+        result.setSuccess(true);
+        return result;
+    }
+
+    @Override
+    public ApiResult<Void> createAsync() {
+        return null;
+    }
+}

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

@@ -20,6 +20,7 @@ import nju.seec.SEECdemo.logic.api.k8s.vo.ApiResult;
 public class NamespaceApiImpl implements NamespaceApi{
 
     private Logger logger = LoggerUtil.getLogger(NamespaceApi.class);
+
     @Autowired
     private CoreV1Api coreV1Api;
 
@@ -27,6 +28,7 @@ public class NamespaceApiImpl implements NamespaceApi{
      * @todo 此处没有明确项目的LimitRange
      * @param namespace
      * @return
+     * @see NamespaceApi#create(Namespace)
      */
     @Override
     public ApiResult<Void> create(Namespace namespace) {

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

@@ -2,7 +2,9 @@ package nju.seec.SEECdemo.logic.api.k8s.util;
 
 import io.kubernetes.client.ApiClient;
 import io.kubernetes.client.Configuration;
+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;
@@ -17,6 +19,10 @@ public class BeanAnnouncement {
 
     private final CoreV1Api coreV1Api;
 
+    private final AppsV1Api appsV1Api;
+
+    private final ExtensionsV1beta1Api extensionsV1beta1Api;
+
     @Autowired
     public BeanAnnouncement(ApplicationProperties applicationProperties) {
         K8s k8s = applicationProperties.getK8s();
@@ -26,13 +32,25 @@ public class BeanAnnouncement {
         ApiClient apiClient = Config.fromToken(apiServer, token);
         Configuration.setDefaultApiClient(apiClient);
         this.coreV1Api = new CoreV1Api();
+        this.appsV1Api = new AppsV1Api();
+        this.extensionsV1beta1Api = new ExtensionsV1beta1Api();
     }
 
     @Bean
-    public CoreV1Api k8sApi(){
+    public CoreV1Api coreV1Api(){
         return coreV1Api;
     }
 
+    @Bean
+    public AppsV1Api appsV1Api() {
+        return appsV1Api;
+    }
+
+    @Bean
+    public ExtensionsV1beta1Api extensionsV1beta1Api(){
+        return extensionsV1beta1Api;
+    }
+
     /**
      * 软工二的项目模板
      * @return

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

@@ -16,7 +16,11 @@ public class ApiResult<T> {
 
     private int errorCode;
 
+    private String errorDescription;
+
     private T value;
 
     public static final int CONFLICT = 409;//命名冲突
+
+    public static final int NOT_EXIST = 800; //资源不存在
 }

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

@@ -3,11 +3,13 @@ package nju.seec.SEECdemo.logic.api.k8s.vo;
 public abstract class Constants {
 
 	//标签域名前缀
-	private static final String LABEL_PREFIX = "nju.seec.SEECdemo";
+	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

+ 75 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Container.java

@@ -0,0 +1,75 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.models.V1Container;
+import io.kubernetes.client.models.V1ContainerBuilder;
+import io.kubernetes.client.models.V1EnvVar;
+import io.kubernetes.client.models.V1EnvVarBuilder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+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;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Data
+@NoArgsConstructor
+public class Container {
+
+    @NotNull
+    @Pattern(regexp = NAME_PATTERN)
+    private String name;
+
+    @NotBlank
+    private String image;
+
+
+    private Set<ContainerPort>  ports = new HashSet<>();
+
+    /* 启动指令的参数 */
+    private List<String> args;
+
+    /* 启动指令 */
+    private List<String> command;
+
+    /* 环境变量 */
+    private Map<String, String> env = new HashMap<>();
+
+    public Container(V1Container v1Container) {
+        this.name = v1Container.getName();
+        this.image = v1Container.getImage();
+        this.ports = v1Container.getPorts().stream().map(ContainerPort::new).collect(Collectors.toSet());
+        this.args = v1Container.getArgs();
+        this.command = v1Container.getCommand();
+        this.env = v1Container.getEnv().stream().collect(Collectors.toMap(V1EnvVar::getName, V1EnvVar::getValue));
+    }
+
+    public V1Container toV1Container(){
+        V1Container container = new V1ContainerBuilder()
+                .withName(name)
+                .withImage(image)
+                .withImagePullPolicy("Always")
+                .withArgs(args)
+                .withCommand(command)
+                .withPorts(ports.stream().map(ContainerPort::toV1ContainerPort).collect(Collectors.toList()))
+                .withEnv(env
+                        .entrySet()
+                        .stream()
+                        .map(entry ->
+                                new V1EnvVarBuilder()
+                                        .withName(entry.getKey())
+                                        .withValue(entry.getValue())
+                                        .build())
+                        .collect(Collectors.toList())
+                )
+                .build();
+        return container;
+    }
+}

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

@@ -0,0 +1,51 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.models.V1ContainerPort;
+import io.kubernetes.client.models.V1ContainerPortBuilder;
+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;
+
+import static nju.seec.SEECdemo.logic.api.k8s.vo.Constants.NAME_PATTERN;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Data
+@EqualsAndHashCode
+@NoArgsConstructor
+public class ContainerPort {
+
+    @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;
+
+    public ContainerPort(V1ContainerPort v1ContainerPort) {
+        ContainerPort containerPort = new ContainerPort();
+        containerPort.setName(v1ContainerPort.getName());
+        containerPort.setPort(v1ContainerPort.getContainerPort());
+        containerPort.setProtocol(v1ContainerPort.getProtocol());
+    }
+    public V1ContainerPort toV1ContainerPort(){
+        V1ContainerPort v1ContainerPort = new V1ContainerPortBuilder()
+                .withContainerPort(port)
+                .withName(name)
+                .withProtocol(protocol)
+                .build();
+        return v1ContainerPort;
+    }
+
+}

+ 109 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/Deployment.java

@@ -0,0 +1,109 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.models.*;
+import lombok.Data;
+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
+ */
+@Data
+@NoArgsConstructor
+public class Deployment {
+
+    private static final String DEPLOYMENT_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应用
+
+    @Range(min = 1, max = 3)
+    private int replicas;
+
+    private List<Container> containers = new ArrayList<>();
+
+    private Set<ServicePort> servicePorts = new HashSet<>();
+
+    public V1Deployment toV1Deployment(){
+        V1Deployment v1Deployment = new V1DeploymentBuilder()
+                .withApiVersion(DEPLOYMENT_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)
+                .withNamespace(namespace)
+                .build();
+    }
+
+    private V1LabelSelector toDeploymentV1LabelSelector() {
+        return new V1LabelSelectorBuilder()
+                .withMatchLabels(Collections.singletonMap(APPLICATION_LABEL, name))
+                .build();
+    }
+
+    private V1PodTemplateSpec toPodSpecTemplate(){
+        V1ObjectMeta v1ObjectMeta = new V1ObjectMetaBuilder()
+                .withLabels(Collections.singletonMap(APPLICATION_LABEL, name))
+                .build();
+        V1PodSpec v1PodSpec = new V1PodSpecBuilder()
+                .addAllToContainers(containers.stream().map(Container::toV1Container).collect(Collectors.toList()))
+                .withImagePullSecrets(new V1LocalObjectReference().name("regcred"))
+                .build();
+        V1PodTemplateSpec v1PodTemplateSpec = new V1PodTemplateSpecBuilder()
+                .withMetadata(v1ObjectMeta)
+                .withSpec(v1PodSpec)
+                .build();
+        return v1PodTemplateSpec;
+    }
+
+    private V1DeploymentSpec toV1DeploymentSpec(){
+        return new V1DeploymentSpecBuilder()
+                .withReplicas(replicas)
+                .withSelector(this.toDeploymentV1LabelSelector())
+                .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;
+    }
+
+}

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

@@ -17,10 +17,9 @@ import java.util.Map;
 @Data
 public class Namespace {
 
-    private static final String VERSION = "v1";
-
     private static final String KIND = "Namespace";
 
+    private static final String VERSION = "v1";
     @NotNull
     private String name;
 

+ 47 - 0
src/main/java/nju/seec/SEECdemo/logic/api/k8s/vo/ServicePort.java

@@ -0,0 +1,47 @@
+package nju.seec.SEECdemo.logic.api.k8s.vo;
+
+import io.kubernetes.client.custom.IntOrString;
+import io.kubernetes.client.models.V1ServicePort;
+import io.kubernetes.client.models.V1ServicePortBuilder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * author: rale
+ * createdAt: 12/23/18
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode
+public class ServicePort {
+
+    @NotNull
+    private String name;
+
+    @Range(min = 1, max = 633)
+    private int port;
+
+    @NotNull
+    private IntOrString targetPort;
+
+    public ServicePort(V1ServicePort v1ServicePort) {
+        this.name = v1ServicePort.getName();
+        this.port = v1ServicePort.getPort();
+        this.targetPort = v1ServicePort.getTargetPort();
+    }
+
+    public V1ServicePort toV1ServicePort(){
+        V1ServicePort v1ServicePort = new V1ServicePortBuilder()
+                .withName(name)
+                .withPort(port)
+                .withTargetPort(targetPort)
+                .build();
+        return v1ServicePort;
+    }
+
+
+}

+ 57 - 0
src/test/java/nju/seec/SEECdemo/service/api/DeploymentApiImplTest.java

@@ -0,0 +1,57 @@
+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 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: 12/24/18
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class DeploymentApiImplTest {
+
+    @Autowired
+    private DeploymentApi deploymentApi;
+
+    @Test
+    public void testCreate() {
+        Deployment deployment = new Deployment();
+        deployment.setName("test");
+        deployment.setNamespace("group24");
+        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");
+        ContainerPort containerPort = new ContainerPort();
+        containerPort.setPort(8081);
+        containerPort.setProtocol("TCP");
+        Set<ContainerPort> ports = new HashSet<>();
+        ports.add(containerPort);
+        container.setPorts(ports);
+        deployment.setContainers(Collections.singletonList(container));
+
+        deploymentApi.createSync(deployment);
+    }
+}