From 88fc4e6de5b33d56c00be5b51104011d6c591536 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 19 Nov 2022 22:16:39 +0900 Subject: [PATCH 01/10] refactor: migrate legacy ab test api to v1 --- .../deployment/service/DeploymentService.java | 2 +- .../service/DeploymentServiceImpl.java | 10 +- .../kubernetes/service/KubernetesService.java | 7 +- .../service/KubernetesServiceImpl.java | 110 +-------------- .../test/dto/service/ABTestCreateDto.java | 19 --- .../{ => v1}/controller/ABTestController.java | 22 +-- .../{ => v1}/dto/mapper/ABTestMapper.java | 10 +- .../{ => v1}/dto/mapper/ABTestMapperImpl.java | 10 +- .../test/v1/dto/request/ABTestElementDto.java | 23 +++ .../dto/request/ABTestRequestDto.java | 2 +- .../dto/response/ABTestCreateResponseDto.java | 9 +- .../dto/response/ABTestDeleteResponseDto.java | 2 +- .../dto/response/ABTestReadResponseDto.java | 9 +- .../dto/service/base/ABTestBaseDto.java} | 10 +- .../dto/service/derived/ABTestCreateDto.java | 16 +++ .../dto/service/derived/ABTestUpdateDto.java | 16 +++ .../domain/test/{ => v1}/entity/ABTest.java | 6 +- .../exception/ABTestExistsException.java | 2 +- .../exception/ABTestNotFoundException.java | 2 +- .../{ => v1}/repository/ABTestRepository.java | 4 +- .../test/{ => v1}/service/ABTestService.java | 15 +- .../{ => v1}/service/ABTestServiceImpl.java | 24 ++-- .../internal/ABTestKubernetesService.java | 11 ++ .../internal/ABTestKubernetesServiceImpl.java | 132 ++++++++++++++++++ .../service/DeploymentServiceTest.java | 10 +- .../service/KubernetesServiceTest.java | 14 +- .../unit/test/service/ABTestServiceTest.java | 16 +-- 27 files changed, 295 insertions(+), 218 deletions(-) delete mode 100644 src/main/java/io/so1s/backend/domain/test/dto/service/ABTestCreateDto.java rename src/main/java/io/so1s/backend/domain/test/{ => v1}/controller/ABTestController.java (80%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/mapper/ABTestMapper.java (57%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/mapper/ABTestMapperImpl.java (84%) create mode 100644 src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/request/ABTestRequestDto.java (93%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/response/ABTestCreateResponseDto.java (66%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/response/ABTestDeleteResponseDto.java (87%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/dto/response/ABTestReadResponseDto.java (67%) rename src/main/java/io/so1s/backend/domain/test/{dto/service/ABTestUpdateDto.java => v1/dto/service/base/ABTestBaseDto.java} (55%) create mode 100644 src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java rename src/main/java/io/so1s/backend/domain/test/{ => v1}/entity/ABTest.java (93%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/exception/ABTestExistsException.java (80%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/exception/ABTestNotFoundException.java (80%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/repository/ABTestRepository.java (75%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/service/ABTestService.java (53%) rename src/main/java/io/so1s/backend/domain/test/{ => v1}/service/ABTestServiceImpl.java (79%) create mode 100644 src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesService.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesServiceImpl.java diff --git a/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentService.java b/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentService.java index 0c90305b..e66e5acc 100644 --- a/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentService.java +++ b/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentService.java @@ -8,7 +8,7 @@ import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; import io.so1s.backend.domain.deployment.exception.DeploymentUpdateFailedException; import io.so1s.backend.domain.resource.entity.Resource; -import io.so1s.backend.domain.test.exception.ABTestExistsException; +import io.so1s.backend.domain.test.v1.exception.ABTestExistsException; import io.so1s.backend.global.error.exception.NodeResourceExceededException; import java.util.List; import java.util.Optional; diff --git a/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentServiceImpl.java b/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentServiceImpl.java index d8d9074b..40d7ce2d 100644 --- a/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentServiceImpl.java +++ b/src/main/java/io/so1s/backend/domain/deployment/service/DeploymentServiceImpl.java @@ -18,9 +18,9 @@ import io.so1s.backend.domain.resource.entity.Resource; import io.so1s.backend.domain.resource.repository.ResourceRepository; import io.so1s.backend.domain.resource.service.ResourceService; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestExistsException; -import io.so1s.backend.domain.test.repository.ABTestRepository; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.exception.ABTestExistsException; +import io.so1s.backend.domain.test.v1.repository.ABTestRepository; import io.so1s.backend.global.error.exception.NodeResourceExceededException; import java.util.List; import java.util.Optional; @@ -80,7 +80,7 @@ public DeploymentDeleteResponseDto deleteDeployment(Long id) throw new ABTestExistsException("해당 디플로이먼트를 사용하고 있는 AB 테스트가 존재합니다.\nAB 테스트를 먼저 삭제해 주세요."); } - boolean result = kubernetesService.deleteDeployment(deployment); + boolean result = kubernetesService.deleteInferenceServer(deployment); if (!result) { return DeploymentDeleteResponseDto.builder() @@ -129,7 +129,7 @@ public boolean updateInference(Deployment deployment) throws DeploymentUpdateFai "AB Test is exist that use Deployment.\nPlease delete the AB Test first."); } - kubernetesService.deleteDeployment(deployment); + kubernetesService.deleteInferenceServer(deployment); return kubernetesService.deployInferenceServer(deployment); } diff --git a/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesService.java b/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesService.java index 004f1595..e0a383f1 100644 --- a/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesService.java +++ b/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesService.java @@ -4,7 +4,6 @@ import io.so1s.backend.domain.deployment.entity.Deployment; import io.so1s.backend.domain.model.entity.ModelMetadata; import io.so1s.backend.domain.resource.entity.Resource; -import io.so1s.backend.domain.test.entity.ABTest; public interface KubernetesService { @@ -27,11 +26,7 @@ public interface KubernetesService { boolean deployInferenceServer( Deployment deployment); - boolean deployABTest(ABTest abTest); - - boolean deleteDeployment(Deployment deployment); - - boolean deleteABTest(ABTest abTest); + boolean deleteInferenceServer(Deployment deployment); boolean createHPA(Deployment deployment, String namespace); diff --git a/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesServiceImpl.java b/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesServiceImpl.java index fef37901..c5d5e9f1 100644 --- a/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesServiceImpl.java +++ b/src/main/java/io/so1s/backend/domain/kubernetes/service/KubernetesServiceImpl.java @@ -33,17 +33,14 @@ import io.so1s.backend.domain.model.entity.Model; import io.so1s.backend.domain.model.entity.ModelMetadata; import io.so1s.backend.domain.resource.entity.Resource; -import io.so1s.backend.domain.test.entity.ABTest; import io.so1s.backend.global.utils.HashGenerator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.task.TaskRejectedException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -364,98 +361,10 @@ public boolean deployInferenceServer( return true; } - @Transactional(readOnly = true) - @Override - public boolean deployABTest(ABTest abTest) { - - String namespace = getNamespace(); - String abTestName = "ab-test-" + abTest.getName().toLowerCase(); - - String host = abTestName + ".so1s.io"; // TODO: Fix hard-coded root domain - - String aName = abTest.getA().getName().toLowerCase(); - String bName = abTest.getB().getName().toLowerCase(); - - Map labels = new HashMap<>(); - labels.put("app", "ab-test"); - labels.put("name", abTestName); - - VirtualService abTestVirtualService = new VirtualServiceBuilder() - .withNewMetadata() - .withName(abTestName) - .withNamespace(namespace) - .addToLabels(labels) - .endMetadata() - .withNewSpec() - .withHosts(host) - .withGateways(abTestName) - .addNewHttp() - .addNewMatch() - .withNewUri() - .withNewStringMatchPrefixType("/") - .endUri() - .endMatch() - .addNewRoute() - .withWeight(50) - .withNewDestination() - .withHost(aName) - .withNewPort() - .withNumber(3000) - .endPort() - .endDestination() - .endRoute() - .addNewRoute() - .withWeight(50) - .withNewDestination() - .withHost(bName) - .withNewPort() - .withNumber(3000) - .endPort() - .endDestination() - .endRoute() - .endHttp() - .endSpec() - .build(); - - Gateway abTestGateway = new GatewayBuilder() - .withNewMetadata() - .withName(abTestName) - .withNamespace(namespace) - .addToLabels(labels) - .endMetadata() - .withNewSpec() - .addNewServer() - .withNewPort() - .withNumber(80) - .withName("http") - .withProtocol("HTTP") - .endPort() - .withHosts(host) - .endServer() - .addNewServer() - .withNewPort() - .withNumber(9443) - .withName("http-dev") - .withProtocol("HTTP") - .endPort() - .withHosts(host) - .endServer() - .endSpec() - .build(); - - try { - istioClient.v1beta1().gateways().inNamespace(namespace).createOrReplace(abTestGateway); - istioClient.v1beta1().virtualServices().inNamespace(namespace) - .createOrReplace(abTestVirtualService); - } catch (KubernetesClientException ignored) { - return false; - } - - return true; - } @Override - public boolean deleteDeployment(io.so1s.backend.domain.deployment.entity.Deployment deployment) { + public boolean deleteInferenceServer( + io.so1s.backend.domain.deployment.entity.Deployment deployment) { String namespace = getNamespace(); String deploymentName = deployment.getName().toLowerCase(); @@ -475,21 +384,6 @@ public boolean deleteDeployment(io.so1s.backend.domain.deployment.entity.Deploym return true; } - @Override - public boolean deleteABTest(ABTest abTest) { - String namespace = getNamespace(); - String abTestName = "ab-test-" + abTest.getName().toLowerCase(); - - try { - istioClient.v1beta1().gateways().inNamespace(namespace).withName(abTestName).delete(); - istioClient.v1beta1().virtualServices().inNamespace(namespace).withName(abTestName).delete(); - } catch (KubernetesClientException ignored) { - return false; - } - - return true; - } - public HasMetadata getDeploymentObject(String name) { List deployments = client.apps().deployments().inNamespace(getNamespace()) .withLabel("app", "inference").list() diff --git a/src/main/java/io/so1s/backend/domain/test/dto/service/ABTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/dto/service/ABTestCreateDto.java deleted file mode 100644 index 47e78173..00000000 --- a/src/main/java/io/so1s/backend/domain/test/dto/service/ABTestCreateDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.so1s.backend.domain.test.dto.service; - -import io.so1s.backend.domain.test.entity.ABTest; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ABTestCreateDto { - - private ABTest entity; - private Boolean success; - -} diff --git a/src/main/java/io/so1s/backend/domain/test/controller/ABTestController.java b/src/main/java/io/so1s/backend/domain/test/v1/controller/ABTestController.java similarity index 80% rename from src/main/java/io/so1s/backend/domain/test/controller/ABTestController.java rename to src/main/java/io/so1s/backend/domain/test/v1/controller/ABTestController.java index 37157c6e..a4ae4800 100644 --- a/src/main/java/io/so1s/backend/domain/test/controller/ABTestController.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/controller/ABTestController.java @@ -1,18 +1,18 @@ -package io.so1s.backend.domain.test.controller; +package io.so1s.backend.domain.test.v1.controller; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; import io.so1s.backend.domain.deployment.service.DeploymentService; import io.so1s.backend.domain.kubernetes.service.KubernetesService; -import io.so1s.backend.domain.test.dto.mapper.ABTestMapper; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestCreateResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestDeleteResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestReadResponseDto; -import io.so1s.backend.domain.test.dto.service.ABTestCreateDto; -import io.so1s.backend.domain.test.dto.service.ABTestUpdateDto; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestNotFoundException; -import io.so1s.backend.domain.test.service.ABTestService; +import io.so1s.backend.domain.test.v1.dto.mapper.ABTestMapper; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestCreateResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestDeleteResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestCreateDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestUpdateDto; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; +import io.so1s.backend.domain.test.v1.service.ABTestService; import java.util.List; import javax.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapper.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapper.java similarity index 57% rename from src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapper.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapper.java index e5822010..bdfe3c02 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapper.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapper.java @@ -1,10 +1,10 @@ -package io.so1s.backend.domain.test.dto.mapper; +package io.so1s.backend.domain.test.v1.dto.mapper; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestCreateResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestReadResponseDto; -import io.so1s.backend.domain.test.entity.ABTest; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestCreateResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v1.entity.ABTest; import org.springframework.dao.DataIntegrityViolationException; public interface ABTestMapper { diff --git a/src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapperImpl.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapperImpl.java similarity index 84% rename from src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapperImpl.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapperImpl.java index 22f11c53..13e16881 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/mapper/ABTestMapperImpl.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/mapper/ABTestMapperImpl.java @@ -1,12 +1,12 @@ -package io.so1s.backend.domain.test.dto.mapper; +package io.so1s.backend.domain.test.v1.dto.mapper; import io.so1s.backend.domain.deployment.entity.Deployment; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; import io.so1s.backend.domain.deployment.service.DeploymentService; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestCreateResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestReadResponseDto; -import io.so1s.backend.domain.test.entity.ABTest; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestCreateResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v1.entity.ABTest; import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java new file mode 100644 index 00000000..5c8bc333 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java @@ -0,0 +1,23 @@ +package io.so1s.backend.domain.test.v1.dto.request; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ABTestElementDto { + + @NotNull + private Long id; + + @NotNull + @Min(value = 0, message = "weight는 음수가 될 수 없습니다.") + private Integer weight; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/dto/request/ABTestRequestDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestRequestDto.java similarity index 93% rename from src/main/java/io/so1s/backend/domain/test/dto/request/ABTestRequestDto.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestRequestDto.java index a0527253..b3e82764 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/request/ABTestRequestDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestRequestDto.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.dto.request; +package io.so1s.backend.domain.test.v1.dto.request; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; diff --git a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestCreateResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestCreateResponseDto.java similarity index 66% rename from src/main/java/io/so1s/backend/domain/test/dto/response/ABTestCreateResponseDto.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestCreateResponseDto.java index fd929518..2b0c6cda 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestCreateResponseDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestCreateResponseDto.java @@ -1,10 +1,13 @@ -package io.so1s.backend.domain.test.dto.response; +package io.so1s.backend.domain.test.v1.dto.response; -import lombok.*; - import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder diff --git a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestDeleteResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestDeleteResponseDto.java similarity index 87% rename from src/main/java/io/so1s/backend/domain/test/dto/response/ABTestDeleteResponseDto.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestDeleteResponseDto.java index 30b573df..c42eabb9 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestDeleteResponseDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestDeleteResponseDto.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.dto.response; +package io.so1s.backend.domain.test.v1.dto.response; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestReadResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestReadResponseDto.java similarity index 67% rename from src/main/java/io/so1s/backend/domain/test/dto/response/ABTestReadResponseDto.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestReadResponseDto.java index c02497b1..b973b509 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/response/ABTestReadResponseDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/response/ABTestReadResponseDto.java @@ -1,9 +1,12 @@ -package io.so1s.backend.domain.test.dto.response; - -import lombok.*; +package io.so1s.backend.domain.test.v1.dto.response; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder diff --git a/src/main/java/io/so1s/backend/domain/test/dto/service/ABTestUpdateDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/base/ABTestBaseDto.java similarity index 55% rename from src/main/java/io/so1s/backend/domain/test/dto/service/ABTestUpdateDto.java rename to src/main/java/io/so1s/backend/domain/test/v1/dto/service/base/ABTestBaseDto.java index 7ed1522b..c3af12f6 100644 --- a/src/main/java/io/so1s/backend/domain/test/dto/service/ABTestUpdateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/base/ABTestBaseDto.java @@ -1,17 +1,17 @@ -package io.so1s.backend.domain.test.dto.service; +package io.so1s.backend.domain.test.v1.dto.service.base; -import io.so1s.backend.domain.test.entity.ABTest; +import io.so1s.backend.domain.test.v1.entity.ABTest; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @Getter -@Builder +@SuperBuilder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ABTestUpdateDto { +public abstract class ABTestBaseDto { private ABTest entity; private Boolean success; diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java new file mode 100644 index 00000000..4b52aefc --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java @@ -0,0 +1,16 @@ +package io.so1s.backend.domain.test.v1.dto.service.derived; + +import io.so1s.backend.domain.test.v1.dto.service.base.ABTestBaseDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABTestCreateDto extends ABTestBaseDto { + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java new file mode 100644 index 00000000..0022cac0 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java @@ -0,0 +1,16 @@ +package io.so1s.backend.domain.test.v1.dto.service.derived; + +import io.so1s.backend.domain.test.v1.dto.service.base.ABTestBaseDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABTestUpdateDto extends ABTestBaseDto { + +} diff --git a/src/main/java/io/so1s/backend/domain/test/entity/ABTest.java b/src/main/java/io/so1s/backend/domain/test/v1/entity/ABTest.java similarity index 93% rename from src/main/java/io/so1s/backend/domain/test/entity/ABTest.java rename to src/main/java/io/so1s/backend/domain/test/v1/entity/ABTest.java index f8aa5192..41868003 100644 --- a/src/main/java/io/so1s/backend/domain/test/entity/ABTest.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/entity/ABTest.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.entity; +package io.so1s.backend.domain.test.v1.entity; import io.so1s.backend.domain.deployment.entity.Deployment; @@ -22,7 +22,7 @@ import lombok.NoArgsConstructor; @Entity -@Table(name = "test") +@Table(name = "ab_test") @Getter @Builder @AllArgsConstructor @@ -31,7 +31,7 @@ public class ABTest extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "test_id") + @Column(name = "ab_test_id") private Long id; @NotBlank diff --git a/src/main/java/io/so1s/backend/domain/test/exception/ABTestExistsException.java b/src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestExistsException.java similarity index 80% rename from src/main/java/io/so1s/backend/domain/test/exception/ABTestExistsException.java rename to src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestExistsException.java index 82d997a7..fbbea28b 100644 --- a/src/main/java/io/so1s/backend/domain/test/exception/ABTestExistsException.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestExistsException.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.exception; +package io.so1s.backend.domain.test.v1.exception; import io.so1s.backend.global.error.exception.ExistException; diff --git a/src/main/java/io/so1s/backend/domain/test/exception/ABTestNotFoundException.java b/src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestNotFoundException.java similarity index 80% rename from src/main/java/io/so1s/backend/domain/test/exception/ABTestNotFoundException.java rename to src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestNotFoundException.java index 81c83e6e..e38390f6 100644 --- a/src/main/java/io/so1s/backend/domain/test/exception/ABTestNotFoundException.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/exception/ABTestNotFoundException.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.exception; +package io.so1s.backend.domain.test.v1.exception; import io.so1s.backend.global.error.exception.NotFoundException; diff --git a/src/main/java/io/so1s/backend/domain/test/repository/ABTestRepository.java b/src/main/java/io/so1s/backend/domain/test/v1/repository/ABTestRepository.java similarity index 75% rename from src/main/java/io/so1s/backend/domain/test/repository/ABTestRepository.java rename to src/main/java/io/so1s/backend/domain/test/v1/repository/ABTestRepository.java index 90787a06..6244ec8d 100644 --- a/src/main/java/io/so1s/backend/domain/test/repository/ABTestRepository.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/repository/ABTestRepository.java @@ -1,6 +1,6 @@ -package io.so1s.backend.domain.test.repository; +package io.so1s.backend.domain.test.v1.repository; -import io.so1s.backend.domain.test.entity.ABTest; +import io.so1s.backend.domain.test.v1.entity.ABTest; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/io/so1s/backend/domain/test/service/ABTestService.java b/src/main/java/io/so1s/backend/domain/test/v1/service/ABTestService.java similarity index 53% rename from src/main/java/io/so1s/backend/domain/test/service/ABTestService.java rename to src/main/java/io/so1s/backend/domain/test/v1/service/ABTestService.java index 7c9aa93a..17fa04ee 100644 --- a/src/main/java/io/so1s/backend/domain/test/service/ABTestService.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/service/ABTestService.java @@ -1,13 +1,12 @@ -package io.so1s.backend.domain.test.service; +package io.so1s.backend.domain.test.v1.service; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestDeleteResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestReadResponseDto; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestNotFoundException; -import io.so1s.backend.domain.test.dto.service.ABTestCreateDto; -import io.so1s.backend.domain.test.dto.service.ABTestUpdateDto; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestDeleteResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestCreateDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestUpdateDto; +import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; import java.util.List; import java.util.Optional; diff --git a/src/main/java/io/so1s/backend/domain/test/service/ABTestServiceImpl.java b/src/main/java/io/so1s/backend/domain/test/v1/service/ABTestServiceImpl.java similarity index 79% rename from src/main/java/io/so1s/backend/domain/test/service/ABTestServiceImpl.java rename to src/main/java/io/so1s/backend/domain/test/v1/service/ABTestServiceImpl.java index 2bbd9c9e..f3ab818a 100644 --- a/src/main/java/io/so1s/backend/domain/test/service/ABTestServiceImpl.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/service/ABTestServiceImpl.java @@ -1,18 +1,18 @@ -package io.so1s.backend.domain.test.service; +package io.so1s.backend.domain.test.v1.service; import io.so1s.backend.domain.deployment.entity.Deployment; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; import io.so1s.backend.domain.deployment.service.DeploymentService; -import io.so1s.backend.domain.kubernetes.service.KubernetesService; -import io.so1s.backend.domain.test.dto.mapper.ABTestMapper; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestDeleteResponseDto; -import io.so1s.backend.domain.test.dto.response.ABTestReadResponseDto; -import io.so1s.backend.domain.test.dto.service.ABTestCreateDto; -import io.so1s.backend.domain.test.dto.service.ABTestUpdateDto; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestNotFoundException; -import io.so1s.backend.domain.test.repository.ABTestRepository; +import io.so1s.backend.domain.test.v1.dto.mapper.ABTestMapper; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestDeleteResponseDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestCreateDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestUpdateDto; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; +import io.so1s.backend.domain.test.v1.repository.ABTestRepository; +import io.so1s.backend.domain.test.v1.service.internal.ABTestKubernetesService; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -25,7 +25,7 @@ public class ABTestServiceImpl implements ABTestService { private final DeploymentService deploymentService; - private final KubernetesService kubernetesService; + private final ABTestKubernetesService kubernetesService; private final ABTestRepository repository; private final ABTestMapper mapper; diff --git a/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesService.java b/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesService.java new file mode 100644 index 00000000..b4f53bbf --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesService.java @@ -0,0 +1,11 @@ +package io.so1s.backend.domain.test.v1.service.internal; + +import io.so1s.backend.domain.test.v1.entity.ABTest; + +public interface ABTestKubernetesService { + + boolean deployABTest(ABTest abTest); + + boolean deleteABTest(ABTest abTest); + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesServiceImpl.java b/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesServiceImpl.java new file mode 100644 index 00000000..16fcf517 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v1/service/internal/ABTestKubernetesServiceImpl.java @@ -0,0 +1,132 @@ +package io.so1s.backend.domain.test.v1.service.internal; + +import io.fabric8.istio.api.networking.v1beta1.Gateway; +import io.fabric8.istio.api.networking.v1beta1.GatewayBuilder; +import io.fabric8.istio.api.networking.v1beta1.VirtualService; +import io.fabric8.istio.api.networking.v1beta1.VirtualServiceBuilder; +import io.fabric8.istio.client.IstioClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.so1s.backend.domain.kubernetes.service.KubernetesService; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ABTestKubernetesServiceImpl implements + ABTestKubernetesService { + + private final KubernetesService kubernetesService; + private final KubernetesClient client; + private final IstioClient istioClient; + + @Transactional(readOnly = true) + @Override + public boolean deployABTest(ABTest abTest) { + + String namespace = kubernetesService.getNamespace(); + String abTestName = "ab-test-" + abTest.getName().toLowerCase(); + + String host = abTestName + ".so1s.io"; // TODO: Fix hard-coded root domain + + String aName = abTest.getA().getName().toLowerCase(); + String bName = abTest.getB().getName().toLowerCase(); + + Map labels = new HashMap<>(); + labels.put("app", "ab-test"); + labels.put("name", abTestName); + + VirtualService abTestVirtualService = new VirtualServiceBuilder() + .withNewMetadata() + .withName(abTestName) + .withNamespace(namespace) + .addToLabels(labels) + .endMetadata() + .withNewSpec() + .withHosts(host) + .withGateways(abTestName) + .addNewHttp() + .addNewMatch() + .withNewUri() + .withNewStringMatchPrefixType("/") + .endUri() + .endMatch() + .addNewRoute() + .withWeight(50) + .withNewDestination() + .withHost(aName) + .withNewPort() + .withNumber(3000) + .endPort() + .endDestination() + .endRoute() + .addNewRoute() + .withWeight(50) + .withNewDestination() + .withHost(bName) + .withNewPort() + .withNumber(3000) + .endPort() + .endDestination() + .endRoute() + .endHttp() + .endSpec() + .build(); + + Gateway abTestGateway = new GatewayBuilder() + .withNewMetadata() + .withName(abTestName) + .withNamespace(namespace) + .addToLabels(labels) + .endMetadata() + .withNewSpec() + .addNewServer() + .withNewPort() + .withNumber(80) + .withName("http") + .withProtocol("HTTP") + .endPort() + .withHosts(host) + .endServer() + .addNewServer() + .withNewPort() + .withNumber(9443) + .withName("http-dev") + .withProtocol("HTTP") + .endPort() + .withHosts(host) + .endServer() + .endSpec() + .build(); + + try { + istioClient.v1beta1().gateways().inNamespace(namespace).createOrReplace(abTestGateway); + istioClient.v1beta1().virtualServices().inNamespace(namespace) + .createOrReplace(abTestVirtualService); + } catch (KubernetesClientException ignored) { + return false; + } + + return true; + } + + + @Override + public boolean deleteABTest(ABTest abTest) { + String namespace = kubernetesService.getNamespace(); + String abTestName = "ab-test-" + abTest.getName().toLowerCase(); + + try { + istioClient.v1beta1().gateways().inNamespace(namespace).withName(abTestName).delete(); + istioClient.v1beta1().virtualServices().inNamespace(namespace).withName(abTestName).delete(); + } catch (KubernetesClientException ignored) { + return false; + } + + return true; + } +} diff --git a/src/test/java/io/so1s/backend/unit/deployment/service/DeploymentServiceTest.java b/src/test/java/io/so1s/backend/unit/deployment/service/DeploymentServiceTest.java index 907c8cbf..036e0285 100644 --- a/src/test/java/io/so1s/backend/unit/deployment/service/DeploymentServiceTest.java +++ b/src/test/java/io/so1s/backend/unit/deployment/service/DeploymentServiceTest.java @@ -36,9 +36,9 @@ import io.so1s.backend.domain.resource.entity.Resource; import io.so1s.backend.domain.resource.repository.ResourceRepository; import io.so1s.backend.domain.resource.service.ResourceService; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestExistsException; -import io.so1s.backend.domain.test.repository.ABTestRepository; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.exception.ABTestExistsException; +import io.so1s.backend.domain.test.v1.repository.ABTestRepository; import io.so1s.backend.global.utils.HashGenerator; import io.so1s.backend.global.vo.Status; import io.so1s.backend.unit.kubernetes.config.TestKubernetesConfig; @@ -58,11 +58,11 @@ import org.springframework.transaction.annotation.Transactional; @Transactional -@SpringBootTest(classes = { TestKubernetesConfig.class }) +@SpringBootTest(classes = {TestKubernetesConfig.class}) @EnableKubernetesMockClient(crud = true) @EnableIstioMockClient(crud = true) @ExtendWith(MockitoExtension.class) -@ActiveProfiles(profiles = { "test" }) +@ActiveProfiles(profiles = {"test"}) public class DeploymentServiceTest { @Autowired diff --git a/src/test/java/io/so1s/backend/unit/kubernetes/service/KubernetesServiceTest.java b/src/test/java/io/so1s/backend/unit/kubernetes/service/KubernetesServiceTest.java index 562b42f6..a89bfd8e 100644 --- a/src/test/java/io/so1s/backend/unit/kubernetes/service/KubernetesServiceTest.java +++ b/src/test/java/io/so1s/backend/unit/kubernetes/service/KubernetesServiceTest.java @@ -15,7 +15,8 @@ import io.so1s.backend.domain.model.entity.Model; import io.so1s.backend.domain.model.entity.ModelMetadata; import io.so1s.backend.domain.resource.entity.Resource; -import io.so1s.backend.domain.test.entity.ABTest; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.service.internal.ABTestKubernetesService; import io.so1s.backend.global.utils.HashGenerator; import io.so1s.backend.global.vo.Status; import io.so1s.backend.unit.kubernetes.config.TestKubernetesConfig; @@ -43,8 +44,11 @@ public class KubernetesServiceTest { @Autowired KubernetesService kubernetesService; + @Autowired + ABTestKubernetesService abTestKubernetesService; KubernetesClient client; + @Test @DisplayName("성공적으로 인퍼런스 잡이 실행되면 true를 반환한다.") public void inferenceServerBuild() throws Exception { @@ -143,7 +147,7 @@ public void deployABTest() throws Exception { boolean result = kubernetesService.deployInferenceServer(a); result = result && kubernetesService.deployInferenceServer(b); - result = result && kubernetesService.deployABTest(abTest); + result = result && abTestKubernetesService.deployABTest(abTest); // then @@ -153,9 +157,9 @@ public void deployABTest() throws Exception { // when - result = kubernetesService.deleteABTest(abTest); - result = result && kubernetesService.deleteDeployment(a); - result = result && kubernetesService.deleteDeployment(b); + result = abTestKubernetesService.deleteABTest(abTest); + result = result && kubernetesService.deleteInferenceServer(a); + result = result && kubernetesService.deleteInferenceServer(b); // then diff --git a/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java b/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java index 2c1a672b..8e786847 100644 --- a/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java +++ b/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java @@ -20,14 +20,14 @@ import io.so1s.backend.domain.model.repository.ModelRepository; import io.so1s.backend.domain.resource.entity.Resource; import io.so1s.backend.domain.resource.repository.ResourceRepository; -import io.so1s.backend.domain.test.dto.request.ABTestRequestDto; -import io.so1s.backend.domain.test.dto.response.ABTestDeleteResponseDto; -import io.so1s.backend.domain.test.dto.service.ABTestCreateDto; -import io.so1s.backend.domain.test.dto.service.ABTestUpdateDto; -import io.so1s.backend.domain.test.entity.ABTest; -import io.so1s.backend.domain.test.exception.ABTestNotFoundException; -import io.so1s.backend.domain.test.repository.ABTestRepository; -import io.so1s.backend.domain.test.service.ABTestService; +import io.so1s.backend.domain.test.v1.dto.request.ABTestRequestDto; +import io.so1s.backend.domain.test.v1.dto.response.ABTestDeleteResponseDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestCreateDto; +import io.so1s.backend.domain.test.v1.dto.service.derived.ABTestUpdateDto; +import io.so1s.backend.domain.test.v1.entity.ABTest; +import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; +import io.so1s.backend.domain.test.v1.repository.ABTestRepository; +import io.so1s.backend.domain.test.v1.service.ABTestService; import io.so1s.backend.global.utils.HashGenerator; import io.so1s.backend.global.vo.Status; import org.junit.jupiter.api.BeforeEach; From f6fa1658e4f1d5c139e440a1df83b24919a9b784 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 19 Nov 2022 22:16:59 +0900 Subject: [PATCH 02/10] feat: add jqwick library for property based testing --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 93887065..ace64f3d 100644 --- a/build.gradle +++ b/build.gradle @@ -146,6 +146,8 @@ dependencies { // implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.4' // implementation 'org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.4' + + testImplementation 'net.jqwik:jqwik:1.7.1' } tasks.named('test') { From 721b0404a549b35d7f7492fddd71ba3cd240e1b5 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 19 Nov 2022 22:17:32 +0900 Subject: [PATCH 03/10] feat: abn test v2 api (WIP) --- .../test/v2/controller/ABNTestController.java | 51 +++++++ .../v2/dto/request/ABNTestRequestDto.java | 30 +++++ .../response/ABNTestDeleteResponseDto.java | 22 +++ .../v2/dto/service/base/ABNTestBaseDto.java | 19 +++ .../dto/service/derived/ABNTestCreateDto.java | 16 +++ .../dto/service/derived/ABNTestUpdateDto.java | 16 +++ .../domain/test/v2/entity/ABNTest.java | 51 +++++++ .../domain/test/v2/entity/ABNTestElement.java | 44 ++++++ .../v2/exception/ABNTestExistsException.java | 10 ++ .../exception/ABNTestNotFoundException.java | 10 ++ .../test/v2/repository/ABNTestRepository.java | 12 ++ .../test/v2/service/ABNTestService.java | 26 ++++ .../test/v2/service/ABNTestServiceImpl.java | 48 +++++++ .../internal/ABNTestKubernetesService.java | 11 ++ .../ABNTestKubernetesServiceImpl.java | 125 ++++++++++++++++++ 15 files changed, 491 insertions(+) create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestDeleteResponseDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/service/base/ABNTestBaseDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTest.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTestElement.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestExistsException.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestNotFoundException.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestRepository.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesService.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesServiceImpl.java diff --git a/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java b/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java new file mode 100644 index 00000000..5e3c996a --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java @@ -0,0 +1,51 @@ +package io.so1s.backend.domain.test.v2.controller; + + +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; +import io.so1s.backend.domain.test.v2.service.ABNTestService; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v2/tests/ab") +@RequiredArgsConstructor +public class ABNTestController { + + private final ABNTestService abnTestService; + + @PostMapping + public ResponseEntity createABNTest( + @Valid @RequestBody ABNTestRequestDto requestDto) { + ABNTestUpdateDto updateDto = abnTestService.createABNTest(requestDto); + ABNTest abnTest = updateDto.getEntity(); + boolean success = updateDto.getSuccess(); + + return null; + } + + @PutMapping + public ResponseEntity updateABNTest( + @Valid @RequestBody ABNTestRequestDto requestDto) { + return null; + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteABNTest(@Valid @PathVariable("id") Long id) + throws ABNTestNotFoundException { + return null; + } + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java new file mode 100644 index 00000000..6f78383f --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java @@ -0,0 +1,30 @@ +package io.so1s.backend.domain.test.v2.dto.request; + +import io.so1s.backend.domain.test.v1.dto.request.ABTestElementDto; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ABNTestRequestDto { + + @NotBlank(message = "name은 공백으로만 이루어지지 않은 3자 이상, 100자 이하의 문자열로 이루어져야 합니다.") + @Size(min = 3, max = 100, message = "name은 3자 이상, 100자 이하의 문자열로 이루어져야 합니다.") + private String name; + + @NotNull(message = "인퍼런스 서버 목록이 주어지지 않았습니다.") + @Size(min = 1, message = "하나 이상의 인퍼런스 서버를 정의해야 합니다.") + private List elements; + + @NotBlank(message = "domain이 주어지지 않았습니다.") + private String domain; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestDeleteResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestDeleteResponseDto.java new file mode 100644 index 00000000..a10ed2b2 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestDeleteResponseDto.java @@ -0,0 +1,22 @@ +package io.so1s.backend.domain.test.v2.dto.response; + + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestDeleteResponseDto { + + @Builder.Default + private Boolean success = Boolean.TRUE; + + @Builder.Default + private String message = ""; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/base/ABNTestBaseDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/base/ABNTestBaseDto.java new file mode 100644 index 00000000..491d6ad4 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/base/ABNTestBaseDto.java @@ -0,0 +1,19 @@ +package io.so1s.backend.domain.test.v2.dto.service.base; + +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class ABNTestBaseDto { + + private ABNTest entity; + private Boolean success; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java new file mode 100644 index 00000000..3f8c99f3 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java @@ -0,0 +1,16 @@ +package io.so1s.backend.domain.test.v2.dto.service.derived; + +import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestCreateDto extends ABNTestBaseDto { + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java new file mode 100644 index 00000000..0e68100e --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java @@ -0,0 +1,16 @@ +package io.so1s.backend.domain.test.v2.dto.service.derived; + +import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestUpdateDto extends ABNTestBaseDto { + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTest.java b/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTest.java new file mode 100644 index 00000000..6dad8272 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTest.java @@ -0,0 +1,51 @@ +package io.so1s.backend.domain.test.v2.entity; + + +import io.so1s.backend.global.entity.BaseTimeEntity; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "abn_test") +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTest extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column + private Long id; + + @NotBlank + @Column(unique = true) + @Size(min = 3, max = 100) + private String name; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "abn_test_element_id") + @NotNull + @Builder.Default + private List elements = new ArrayList<>(); + + @NotBlank + private String domain; +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTestElement.java b/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTestElement.java new file mode 100644 index 00000000..08eae2fe --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/entity/ABNTestElement.java @@ -0,0 +1,44 @@ +package io.so1s.backend.domain.test.v2.entity; + +import io.so1s.backend.domain.deployment.entity.Deployment; +import io.so1s.backend.global.entity.BaseTimeEntity; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestElement extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "abn_test_element_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "deployment_id") + private Deployment deployment; + + @Column + @NotNull + @Min(value = 0, message = "weight는 음수가 될 수 없습니다.") + private Integer weight; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestExistsException.java b/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestExistsException.java new file mode 100644 index 00000000..6c480223 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestExistsException.java @@ -0,0 +1,10 @@ +package io.so1s.backend.domain.test.v2.exception; + +import io.so1s.backend.global.error.exception.ExistException; + +public class ABNTestExistsException extends ExistException { + + public ABNTestExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestNotFoundException.java b/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestNotFoundException.java new file mode 100644 index 00000000..869d55b4 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/exception/ABNTestNotFoundException.java @@ -0,0 +1,10 @@ +package io.so1s.backend.domain.test.v2.exception; + +import io.so1s.backend.global.error.exception.NotFoundException; + +public class ABNTestNotFoundException extends NotFoundException { + + public ABNTestNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestRepository.java b/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestRepository.java new file mode 100644 index 00000000..bc435874 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestRepository.java @@ -0,0 +1,12 @@ +package io.so1s.backend.domain.test.v2.repository; + +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ABNTestRepository extends JpaRepository { + + Optional findByName(String name); + + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java new file mode 100644 index 00000000..8c74ad81 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java @@ -0,0 +1,26 @@ +package io.so1s.backend.domain.test.v2.service; + +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; +import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; +import java.util.List; +import java.util.Optional; + +public interface ABNTestService { + + ABNTestCreateDto createABNTest(ABNTestRequestDto requestDto) + throws DeploymentNotFoundException; + + ABNTestUpdateDto updateABNTest(ABNTestRequestDto requestDto) + throws ABNTestNotFoundException, DeploymentNotFoundException; + + ABNTestDeleteResponseDto deleteABNTest(Long id) throws ABNTestNotFoundException; + + List findAll(); + + Optional findbyId(Long id); +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java new file mode 100644 index 00000000..adfa0543 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java @@ -0,0 +1,48 @@ +package io.so1s.backend.domain.test.v2.service; + +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; +import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; +import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; +import io.so1s.backend.domain.test.v2.repository.ABNTestRepository; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ABNTestServiceImpl implements ABNTestService { + + private final ABNTestRepository abnTestRepository; + + @Override + public ABNTestCreateDto createABNTest(ABNTestRequestDto requestDto) + throws DeploymentNotFoundException { + return null; + } + + @Override + public ABNTestUpdateDto updateABNTest(ABNTestRequestDto requestDto) + throws ABNTestNotFoundException, DeploymentNotFoundException { + return null; + } + + @Override + public ABNTestDeleteResponseDto deleteABNTest(Long id) throws ABNTestNotFoundException { + return null; + } + + @Override + public List findAll() { + return null; + } + + @Override + public Optional findbyId(Long id) { + return Optional.empty(); + } +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesService.java b/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesService.java new file mode 100644 index 00000000..8f71fb0d --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesService.java @@ -0,0 +1,11 @@ +package io.so1s.backend.domain.test.v2.service.internal; + +import io.so1s.backend.domain.test.v2.entity.ABNTest; + +public interface ABNTestKubernetesService { + + boolean deployABNTest(ABNTest abTest); + + boolean deleteABNTest(ABNTest abTest); + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesServiceImpl.java b/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesServiceImpl.java new file mode 100644 index 00000000..4442f69e --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/internal/ABNTestKubernetesServiceImpl.java @@ -0,0 +1,125 @@ +package io.so1s.backend.domain.test.v2.service.internal; + +import io.fabric8.istio.api.networking.v1beta1.Gateway; +import io.fabric8.istio.api.networking.v1beta1.GatewayBuilder; +import io.fabric8.istio.api.networking.v1beta1.VirtualService; +import io.fabric8.istio.api.networking.v1beta1.VirtualServiceBuilder; +import io.fabric8.istio.client.IstioClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.so1s.backend.domain.kubernetes.service.KubernetesService; +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import io.so1s.backend.domain.test.v2.entity.ABNTestElement; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ABNTestKubernetesServiceImpl implements + ABNTestKubernetesService { + + private final KubernetesService kubernetesService; + private final KubernetesClient client; + private final IstioClient istioClient; + + @Override + public boolean deployABNTest(ABNTest abnTest) { + String namespace = kubernetesService.getNamespace(); + String fullName = "abn-test-" + abnTest.getName().toLowerCase(); + + String endpoint = String.format("%s.%s", fullName, abnTest.getDomain()); + + List elements = abnTest.getElements(); + + Map labels = new HashMap<>(); + labels.put("app", "abn-test"); + labels.put("name", fullName); + + var progress = new VirtualServiceBuilder() + .withNewMetadata() + .withName(fullName) + .withNamespace(namespace) + .addToLabels(labels) + .endMetadata() + .withNewSpec() + .withHosts(endpoint) + .withGateways(fullName) + .addNewHttp() + .addNewMatch() + .withNewUri() + .withNewStringMatchPrefixType("/") + .endUri() + .endMatch(); + + for (var element : elements) { + progress = progress.addNewRoute() + .withWeight(element.getWeight()) + .withNewDestination() + .withHost(element.getDeployment().getName().toLowerCase()) + .withNewPort() + .withNumber(3000) + .endPort() + .endDestination() + .endRoute(); + } + + VirtualService virtualService = progress + .endHttp() + .endSpec() + .build(); + + Gateway gateway = new GatewayBuilder() + .withNewMetadata() + .withName(fullName) + .withNamespace(namespace) + .addToLabels(labels) + .endMetadata() + .withNewSpec() + .addNewServer() + .withNewPort() + .withNumber(80) + .withName("http") + .withProtocol("HTTP") + .endPort() + .withHosts(endpoint) + .endServer() + .addNewServer() + .withNewPort() + .withNumber(9443) + .withName("http-dev") + .withProtocol("HTTP") + .endPort() + .withHosts(endpoint) + .endServer() + .endSpec() + .build(); + + try { + istioClient.v1beta1().gateways().inNamespace(namespace).createOrReplace(gateway); + istioClient.v1beta1().virtualServices().inNamespace(namespace) + .createOrReplace(virtualService); + } catch (KubernetesClientException ignored) { + return false; + } + + return true; + } + + @Override + public boolean deleteABNTest(ABNTest abTest) { + String namespace = kubernetesService.getNamespace(); + String abTestName = "abn-test-" + abTest.getName().toLowerCase(); + + try { + istioClient.v1beta1().gateways().inNamespace(namespace).withName(abTestName).delete(); + istioClient.v1beta1().virtualServices().inNamespace(namespace).withName(abTestName).delete(); + } catch (KubernetesClientException ignored) { + return false; + } + + return true; + } +} From c5e91bb232c02b91acfe740f04c0f46b5ea8f64a Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 19 Nov 2022 23:00:46 +0900 Subject: [PATCH 04/10] feat: abn test v2 api --- .../test/v2/controller/ABNTestController.java | 45 ++++++++++--- .../dto/common/ABNTestElementDto.java} | 8 +-- .../v2/dto/mapper/ABNTestElementMapper.java | 13 ++++ .../dto/mapper/ABNTestElementMapperImpl.java | 44 +++++++++++++ .../test/v2/dto/mapper/ABNTestMapper.java | 18 ++++++ .../test/v2/dto/mapper/ABNTestMapperImpl.java | 63 +++++++++++++++++++ .../v2/dto/request/ABNTestRequestDto.java | 6 +- .../response/ABNTestCreateResponseDto.java | 26 ++++++++ .../dto/response/ABNTestReadResponseDto.java | 34 ++++++++++ .../dto/service/derived/ABNTestCreateDto.java | 4 +- .../repository/ABNTestElementRepository.java | 12 ++++ .../test/v2/service/ABNTestService.java | 6 +- .../test/v2/service/ABNTestServiceImpl.java | 51 ++++++++++++--- 13 files changed, 301 insertions(+), 29 deletions(-) rename src/main/java/io/so1s/backend/domain/test/{v1/dto/request/ABTestElementDto.java => v2/dto/common/ABNTestElementDto.java} (65%) create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapper.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapperImpl.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapper.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapperImpl.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestCreateResponseDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestReadResponseDto.java create mode 100644 src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestElementRepository.java diff --git a/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java b/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java index 5e3c996a..1a11e8ab 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/controller/ABNTestController.java @@ -1,17 +1,20 @@ package io.so1s.backend.domain.test.v2.controller; +import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestReadResponseDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; -import io.so1s.backend.domain.test.v2.entity.ABNTest; import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; import io.so1s.backend.domain.test.v2.service.ABNTestService; +import java.util.List; import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -24,28 +27,54 @@ @RequiredArgsConstructor public class ABNTestController { - private final ABNTestService abnTestService; + private final ABNTestService service; + + @GetMapping + public ResponseEntity> findDeployments() { + + return ResponseEntity.ok(service.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findDeployment(@Valid @PathVariable("id") Long id) + throws ABNTestNotFoundException { + + return ResponseEntity.ok(service.findById(id) + .orElseThrow(() -> new ABTestNotFoundException("주어진 AB Test id와 일치하는 객체를 찾지 못했습니다."))); + } @PostMapping public ResponseEntity createABNTest( @Valid @RequestBody ABNTestRequestDto requestDto) { - ABNTestUpdateDto updateDto = abnTestService.createABNTest(requestDto); - ABNTest abnTest = updateDto.getEntity(); - boolean success = updateDto.getSuccess(); + ABNTestCreateDto createDto = service.createABNTest(requestDto); - return null; + return ResponseEntity.ok(ABNTestCreateDto.builder() + .entity(createDto.getEntity()) + .success(createDto.getSuccess()) + .build()); } @PutMapping public ResponseEntity updateABNTest( @Valid @RequestBody ABNTestRequestDto requestDto) { - return null; + ABNTestUpdateDto updateDto = service.updateABNTest(requestDto); + + return ResponseEntity.ok(ABNTestCreateDto.builder() + .entity(updateDto.getEntity()) + .success(updateDto.getSuccess()) + .build()); } @DeleteMapping("/{id}") public ResponseEntity deleteABNTest(@Valid @PathVariable("id") Long id) throws ABNTestNotFoundException { - return null; + + ABNTestDeleteResponseDto deleteDto = service.deleteABNTest(id); + + return ResponseEntity.ok(ABNTestDeleteResponseDto.builder() + .success(deleteDto.getSuccess()) + .message(deleteDto.getMessage()) + .build()); } } diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/common/ABNTestElementDto.java similarity index 65% rename from src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java rename to src/main/java/io/so1s/backend/domain/test/v2/dto/common/ABNTestElementDto.java index 5c8bc333..b29721cf 100644 --- a/src/main/java/io/so1s/backend/domain/test/v1/dto/request/ABTestElementDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/common/ABNTestElementDto.java @@ -1,4 +1,4 @@ -package io.so1s.backend.domain.test.v1.dto.request; +package io.so1s.backend.domain.test.v2.dto.common; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; @@ -11,12 +11,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor -public class ABTestElementDto { +public class ABNTestElementDto { @NotNull - private Long id; + private Long deploymentId; - @NotNull + @NotNull(message = "인퍼런스 서버 목록이 주어지지 않았습니다.") @Min(value = 0, message = "weight는 음수가 될 수 없습니다.") private Integer weight; diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapper.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapper.java new file mode 100644 index 00000000..9ccd9803 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapper.java @@ -0,0 +1,13 @@ +package io.so1s.backend.domain.test.v2.dto.mapper; + +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.test.v2.dto.common.ABNTestElementDto; +import io.so1s.backend.domain.test.v2.entity.ABNTestElement; + +public interface ABNTestElementMapper { + + ABNTestElement toElement(ABNTestElementDto dto) + throws DeploymentNotFoundException; + + ABNTestElementDto toDto(ABNTestElement entity); +} \ No newline at end of file diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapperImpl.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapperImpl.java new file mode 100644 index 00000000..7a337efc --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestElementMapperImpl.java @@ -0,0 +1,44 @@ +package io.so1s.backend.domain.test.v2.dto.mapper; + +import io.so1s.backend.domain.deployment.entity.Deployment; +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.deployment.repository.DeploymentRepository; +import io.so1s.backend.domain.test.v2.dto.common.ABNTestElementDto; +import io.so1s.backend.domain.test.v2.entity.ABNTestElement; +import io.so1s.backend.domain.test.v2.repository.ABNTestElementRepository; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ABNTestElementMapperImpl implements ABNTestElementMapper { + + private final ABNTestElementRepository elementRepository; + private final DeploymentRepository deploymentRepository; + + @Override + public ABNTestElement toElement(ABNTestElementDto dto) throws DeploymentNotFoundException { + + Deployment deployment = deploymentRepository.findById(dto.getDeploymentId()) + .orElseThrow(() -> new DeploymentNotFoundException( + String.format("주어진 id %d와(과) 일치하는 Deployment를 찾지 못했습니다.", dto.getDeploymentId()))); + + return elementRepository.findByDeployment_Id(deployment.getId()) + .orElseGet( + () -> elementRepository.save( + ABNTestElement.builder() + .deployment(deployment) + .weight(dto.getWeight()) + .build())); + } + + @Transactional + @Override + public ABNTestElementDto toDto(ABNTestElement entity) { + return ABNTestElementDto.builder() + .deploymentId(entity.getDeployment().getId()) + .weight(entity.getWeight()) + .build(); + } +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapper.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapper.java new file mode 100644 index 00000000..1f40e26c --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapper.java @@ -0,0 +1,18 @@ +package io.so1s.backend.domain.test.v2.dto.mapper; + +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestCreateResponseDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestReadResponseDto; +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import org.springframework.dao.DataIntegrityViolationException; + +public interface ABNTestMapper { + + ABNTest toABNTest(ABNTestRequestDto dto) + throws DeploymentNotFoundException, DataIntegrityViolationException; + + ABNTestReadResponseDto toReadDto(ABNTest entity); + + ABNTestCreateResponseDto toCreateDto(Boolean success, String message, ABNTest entity); +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapperImpl.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapperImpl.java new file mode 100644 index 00000000..c3ce5e31 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/mapper/ABNTestMapperImpl.java @@ -0,0 +1,63 @@ +package io.so1s.backend.domain.test.v2.dto.mapper; + +import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; +import io.so1s.backend.domain.deployment.service.DeploymentService; +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestCreateResponseDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestReadResponseDto; +import io.so1s.backend.domain.test.v2.entity.ABNTest; +import io.so1s.backend.domain.test.v2.repository.ABNTestElementRepository; +import java.util.stream.Collectors; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ABNTestMapperImpl implements ABNTestMapper { + + private final DeploymentService deploymentService; + private final ABNTestElementRepository elementRepository; + private final ABNTestElementMapper elementMapper; + + + @Override + public ABNTest toABNTest(ABNTestRequestDto dto) + throws DeploymentNotFoundException, DataIntegrityViolationException { + var elements = dto.getElements().stream() + .map(elementMapper::toElement) + .collect(Collectors.toList()); + + return ABNTest.builder() + .name(dto.getName()) + .elements(elements) + .domain(dto.getDomain()) + .build(); + } + + + @Transactional + @Override + public ABNTestReadResponseDto toReadDto(ABNTest entity) { + var elements = entity.getElements().stream() + .map(elementMapper::toDto) + .collect(Collectors.toList()); + + return ABNTestReadResponseDto.builder() + .id(entity.getId()) + .name(entity.getName()) + .elements(elements) + .domain(entity.getDomain()) + .build(); + } + + @Override + public ABNTestCreateResponseDto toCreateDto(Boolean success, String message, ABNTest entity) { + return ABNTestCreateResponseDto.builder() + .success(success) + .message(message) + .data(toReadDto(entity)) + .build(); + } +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java index 6f78383f..136dd83d 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/request/ABNTestRequestDto.java @@ -1,6 +1,6 @@ package io.so1s.backend.domain.test.v2.dto.request; -import io.so1s.backend.domain.test.v1.dto.request.ABTestElementDto; +import io.so1s.backend.domain.test.v2.dto.common.ABNTestElementDto; import java.util.List; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @@ -21,8 +21,8 @@ public class ABNTestRequestDto { private String name; @NotNull(message = "인퍼런스 서버 목록이 주어지지 않았습니다.") - @Size(min = 1, message = "하나 이상의 인퍼런스 서버를 정의해야 합니다.") - private List elements; + @Size(min = 1, message = "하나 이상의 인퍼런스 서버가 필요합니다.") + private List elements; @NotBlank(message = "domain이 주어지지 않았습니다.") private String domain; diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestCreateResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestCreateResponseDto.java new file mode 100644 index 00000000..20d908b9 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestCreateResponseDto.java @@ -0,0 +1,26 @@ +package io.so1s.backend.domain.test.v2.dto.response; + + +import javax.annotation.Nullable; +import javax.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestCreateResponseDto { + + @Builder.Default + private Boolean success = Boolean.TRUE; + + @NotBlank + private String message; + + @Nullable + private ABNTestReadResponseDto data; +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestReadResponseDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestReadResponseDto.java new file mode 100644 index 00000000..2a5932c1 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/response/ABNTestReadResponseDto.java @@ -0,0 +1,34 @@ +package io.so1s.backend.domain.test.v2.dto.response; + +import io.so1s.backend.domain.test.v2.dto.common.ABNTestElementDto; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ABNTestReadResponseDto { + + @NotNull + private Long id; + + @NotBlank + private String name; + + + @NotNull + @Size(min = 1, message = "하나 이상의 인퍼런스 서버가 필요합니다.") + private List elements; + + @NotBlank + private String domain; + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java index 3f8c99f3..74acf307 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java @@ -3,12 +3,12 @@ import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @Getter -@Builder +@SuperBuilder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ABNTestCreateDto extends ABNTestBaseDto { diff --git a/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestElementRepository.java b/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestElementRepository.java new file mode 100644 index 00000000..45a30d12 --- /dev/null +++ b/src/main/java/io/so1s/backend/domain/test/v2/repository/ABNTestElementRepository.java @@ -0,0 +1,12 @@ +package io.so1s.backend.domain.test.v2.repository; + +import io.so1s.backend.domain.test.v2.entity.ABNTestElement; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ABNTestElementRepository extends JpaRepository { + + Optional findByDeployment_Id(Long id); + + +} diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java index 8c74ad81..3307db99 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestService.java @@ -1,9 +1,9 @@ package io.so1s.backend.domain.test.v2.service; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; -import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestReadResponseDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; @@ -20,7 +20,7 @@ ABNTestUpdateDto updateABNTest(ABNTestRequestDto requestDto) ABNTestDeleteResponseDto deleteABNTest(Long id) throws ABNTestNotFoundException; - List findAll(); + List findAll(); - Optional findbyId(Long id); + Optional findById(Long id); } diff --git a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java index adfa0543..962e4370 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/service/ABNTestServiceImpl.java @@ -1,15 +1,19 @@ package io.so1s.backend.domain.test.v2.service; import io.so1s.backend.domain.deployment.exception.DeploymentNotFoundException; -import io.so1s.backend.domain.test.v1.dto.response.ABTestReadResponseDto; +import io.so1s.backend.domain.test.v2.dto.mapper.ABNTestMapper; import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; import io.so1s.backend.domain.test.v2.dto.response.ABNTestDeleteResponseDto; +import io.so1s.backend.domain.test.v2.dto.response.ABNTestReadResponseDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestCreateDto; import io.so1s.backend.domain.test.v2.dto.service.derived.ABNTestUpdateDto; +import io.so1s.backend.domain.test.v2.entity.ABNTest; import io.so1s.backend.domain.test.v2.exception.ABNTestNotFoundException; import io.so1s.backend.domain.test.v2.repository.ABNTestRepository; +import io.so1s.backend.domain.test.v2.service.internal.ABNTestKubernetesService; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,32 +21,61 @@ @RequiredArgsConstructor public class ABNTestServiceImpl implements ABNTestService { - private final ABNTestRepository abnTestRepository; + private final ABNTestKubernetesService kubernetesService; + private final ABNTestRepository repository; + private final ABNTestMapper mapper; @Override public ABNTestCreateDto createABNTest(ABNTestRequestDto requestDto) throws DeploymentNotFoundException { - return null; + + ABNTest entity = repository.save(mapper.toABNTest(requestDto)); + boolean success = kubernetesService.deployABNTest(entity); + + return ABNTestCreateDto.builder() + .success(success) + .entity(entity) + .build(); } @Override public ABNTestUpdateDto updateABNTest(ABNTestRequestDto requestDto) throws ABNTestNotFoundException, DeploymentNotFoundException { - return null; + + String name = requestDto.getName(); + + repository.findByName(name) + .orElseThrow(() -> new ABNTestNotFoundException( + String.format("name %s와(과) 일치하는 ABN 테스트를 찾지 못했습니다.", name))); + + ABNTest entity = repository.save(mapper.toABNTest(requestDto)); + boolean success = kubernetesService.deployABNTest(entity); + + return ABNTestUpdateDto.builder() + .success(success) + .entity(entity) + .build(); } @Override public ABNTestDeleteResponseDto deleteABNTest(Long id) throws ABNTestNotFoundException { - return null; + + ABNTest entity = repository.findById(id) + .orElseThrow(() -> new ABNTestNotFoundException( + String.format("id %d와(과) 일치하는 ABN 테스트를 찾지 못했습니다.", id))); + + boolean success = kubernetesService.deleteABNTest(entity); + + return ABNTestDeleteResponseDto.builder().success(success).build(); } @Override - public List findAll() { - return null; + public List findAll() { + return repository.findAll().stream().map(mapper::toReadDto).collect(Collectors.toList()); } @Override - public Optional findbyId(Long id) { - return Optional.empty(); + public Optional findById(Long id) { + return repository.findById(id).map(mapper::toReadDto); } } From c70eef929b5b6a676cd51b3ac64bd713a04c346e Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sat, 19 Nov 2022 23:08:19 +0900 Subject: [PATCH 05/10] fix: test working --- .../v1/dto/service/derived/ABTestCreateDto.java | 5 ----- .../v1/dto/service/derived/ABTestUpdateDto.java | 5 ----- .../v2/dto/service/derived/ABNTestCreateDto.java | 6 ------ .../v2/dto/service/derived/ABNTestUpdateDto.java | 5 ----- .../unit/test/service/ABTestServiceTest.java | 13 ++++++++----- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java index 4b52aefc..03c90abb 100644 --- a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestCreateDto.java @@ -1,16 +1,11 @@ package io.so1s.backend.domain.test.v1.dto.service.derived; import io.so1s.backend.domain.test.v1.dto.service.base.ABTestBaseDto; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Getter @SuperBuilder -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ABTestCreateDto extends ABTestBaseDto { } diff --git a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java index 0022cac0..0a4d45b0 100644 --- a/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v1/dto/service/derived/ABTestUpdateDto.java @@ -1,16 +1,11 @@ package io.so1s.backend.domain.test.v1.dto.service.derived; import io.so1s.backend.domain.test.v1.dto.service.base.ABTestBaseDto; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Getter @SuperBuilder -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ABTestUpdateDto extends ABTestBaseDto { } diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java index 74acf307..82bd7011 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java @@ -1,16 +1,10 @@ package io.so1s.backend.domain.test.v2.dto.service.derived; import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Getter @SuperBuilder -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ABNTestCreateDto extends ABNTestBaseDto { } diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java index 0e68100e..c857d8b2 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestUpdateDto.java @@ -1,16 +1,11 @@ package io.so1s.backend.domain.test.v2.dto.service.derived; import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Getter @SuperBuilder -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ABNTestUpdateDto extends ABNTestBaseDto { } diff --git a/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java b/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java index 8e786847..ad7bd3a5 100644 --- a/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java +++ b/src/test/java/io/so1s/backend/unit/test/service/ABTestServiceTest.java @@ -28,6 +28,7 @@ import io.so1s.backend.domain.test.v1.exception.ABTestNotFoundException; import io.so1s.backend.domain.test.v1.repository.ABTestRepository; import io.so1s.backend.domain.test.v1.service.ABTestService; +import io.so1s.backend.domain.test.v1.service.internal.ABTestKubernetesService; import io.so1s.backend.global.utils.HashGenerator; import io.so1s.backend.global.vo.Status; import org.junit.jupiter.api.BeforeEach; @@ -67,6 +68,8 @@ public class ABTestServiceTest { LibraryRepository libraryRepository; @MockBean KubernetesService kubernetesService; + @MockBean + ABTestKubernetesService abTestKubernetesService; Library library; Model model; @@ -173,7 +176,7 @@ public void createABTest() throws Exception { .name(baseRequestDto.getName()) .domain(baseRequestDto.getDomain()).build(); - given(kubernetesService.deployABTest(any())).willReturn(true); + given(abTestKubernetesService.deployABTest(any())).willReturn(true); // when ABTestCreateDto createDto = abTestService.createABTest(abTestRequestDto); @@ -194,7 +197,7 @@ public void createABTest() throws Exception { // Clean up // given - given(kubernetesService.deleteABTest(any())).willReturn(true); + given(abTestKubernetesService.deleteABTest(any())).willReturn(true); // when ABTestDeleteResponseDto deleteResponseDto = abTestService.deleteABTest(abTest.getId()); @@ -216,7 +219,7 @@ public void createABTestFailed() throws Exception { .domain(baseRequestDto.getDomain()) .build(); - given(kubernetesService.deployABTest(any())).willReturn(false); + given(abTestKubernetesService.deployABTest(any())).willReturn(false); // when ABTestCreateDto createDto = abTestService.createABTest(abTestRequestDto); @@ -237,7 +240,7 @@ public void createABTestFailed() throws Exception { // Clean up // given - given(kubernetesService.deleteABTest(any())).willReturn(false); + given(abTestKubernetesService.deleteABTest(any())).willReturn(false); // when ABTestDeleteResponseDto deleteResponseDto = abTestService.deleteABTest(abTest.getId()); @@ -322,7 +325,7 @@ public void updateABTest() throws Exception { .domain(baseRequestDto.getDomain()) .build(); - given(kubernetesService.deployABTest(any())).willReturn(true); + given(abTestKubernetesService.deployABTest(any())).willReturn(true); abTestService.createABTest(abTestCreateRequestDto); From aa3f02c7f072c583c7131bcc4ee336e511c66085 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 20 Nov 2022 00:16:52 +0900 Subject: [PATCH 06/10] fix: import @Getter --- .../domain/test/v2/dto/service/derived/ABNTestCreateDto.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java index 82bd7011..0b87a686 100644 --- a/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java +++ b/src/main/java/io/so1s/backend/domain/test/v2/dto/service/derived/ABNTestCreateDto.java @@ -1,6 +1,7 @@ package io.so1s.backend.domain.test.v2.dto.service.derived; import io.so1s.backend.domain.test.v2.dto.service.base.ABNTestBaseDto; +import lombok.Getter; import lombok.experimental.SuperBuilder; @Getter From b4949527a6f9908772513aa9a175258fe0df49aa Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 20 Nov 2022 00:17:20 +0900 Subject: [PATCH 07/10] chore: remove jqwik dependency --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index ace64f3d..93887065 100644 --- a/build.gradle +++ b/build.gradle @@ -146,8 +146,6 @@ dependencies { // implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.4' // implementation 'org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.4' - - testImplementation 'net.jqwik:jqwik:1.7.1' } tasks.named('test') { From a76e19272dba71893826c8ae59d72529bc312631 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 20 Nov 2022 00:22:37 +0900 Subject: [PATCH 08/10] feat: implement example test --- .../unit/test/service/ABNTestServiceTest.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java diff --git a/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java b/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java new file mode 100644 index 00000000..86655de6 --- /dev/null +++ b/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java @@ -0,0 +1,165 @@ +package io.so1s.backend.unit.test.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.fabric8.istio.mock.EnableIstioMockClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.so1s.backend.domain.deployment.dto.request.Standard; +import io.so1s.backend.domain.deployment.entity.Deployment; +import io.so1s.backend.domain.deployment.repository.DeploymentRepository; +import io.so1s.backend.domain.kubernetes.service.KubernetesService; +import io.so1s.backend.domain.library.entity.Library; +import io.so1s.backend.domain.library.repository.LibraryRepository; +import io.so1s.backend.domain.model.entity.Model; +import io.so1s.backend.domain.model.entity.ModelMetadata; +import io.so1s.backend.domain.model.repository.ModelMetadataRepository; +import io.so1s.backend.domain.model.repository.ModelRepository; +import io.so1s.backend.domain.resource.entity.Resource; +import io.so1s.backend.domain.resource.repository.ResourceRepository; +import io.so1s.backend.domain.test.v2.controller.ABNTestController; +import io.so1s.backend.domain.test.v2.dto.common.ABNTestElementDto; +import io.so1s.backend.domain.test.v2.dto.request.ABNTestRequestDto; +import io.so1s.backend.domain.test.v2.service.ABNTestService; +import io.so1s.backend.domain.test.v2.service.internal.ABNTestKubernetesService; +import io.so1s.backend.global.utils.HashGenerator; +import io.so1s.backend.global.vo.Status; +import io.so1s.backend.unit.kubernetes.config.TestKubernetesConfig; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@EnableKubernetesMockClient(crud = true) +@EnableIstioMockClient(crud = true) +@ExtendWith(MockitoExtension.class) +@WithMockUser +@SpringBootTest(classes = {TestKubernetesConfig.class}) +@ActiveProfiles(profiles = {"test"}) +public class ABNTestServiceTest { + + @Autowired + ABNTestController controller; + @Autowired + ABNTestService service; + @Autowired + DeploymentRepository deploymentRepository; + @Autowired + LibraryRepository libraryRepository; + @Autowired + ModelRepository modelRepository; + @Autowired + ModelMetadataRepository modelMetadataRepository; + @Autowired + ResourceRepository resourceRepository; + @MockBean + KubernetesService kubernetesService; + @MockBean + ABNTestKubernetesService abTestKubernetesService; + @Autowired + ObjectMapper objectMapper; + + Library library; + Model model; + ModelMetadata modelMetadata; + Resource resource; + Deployment a; + Deployment b; + + @BeforeEach + public void setUp() { + given(abTestKubernetesService.deployABNTest(any())).willReturn(true); + given(abTestKubernetesService.deleteABNTest(any())).willReturn(true); + + library = libraryRepository.save(Library.builder().name("torch-test").build()); + + model = modelRepository.save(Model.builder() + .name("testModel") + .library(library) + .build()); + + modelMetadata = modelMetadataRepository.save(ModelMetadata.builder() + .status(Status.SUCCEEDED) + .version(HashGenerator.sha256()) + .fileName("titanic.h5") + .url("https://s3.test.com/") + .inputShape("(10,)") + .inputDtype("float32") + .outputShape("(1,)") + .outputDtype("float32") + .deviceType("cpu") + .model(model) + .build()); + + resource = resourceRepository.save(Resource.builder() + .name("ABTestServiceTest") + .cpu("1") + .memory("1Gi") + .gpu("0") + .cpuLimit("2") + .memoryLimit("2Gi") + .gpuLimit("0") + .build()); + + a = deploymentRepository.save(Deployment.builder() + .name("aDeployment") + .status(Status.PENDING) + .standard(Standard.LATENCY) + .standardValue(20) + .maxReplicas(10) + .minReplicas(1) + .modelMetadata(modelMetadata) + .endPoint("a.so1s.io") + .resource(resource) + .build()); + + b = deploymentRepository.save(Deployment.builder() + .name("bDeployment") + .status(Status.PENDING) + .standard(Standard.LATENCY) + .standardValue(20) + .maxReplicas(10) + .minReplicas(1) + .modelMetadata(modelMetadata) + .endPoint("b.so1s.io") + .resource(resource) + .build()); + } + + @Test + @DisplayName("ABN 테스트를 생성하고 삭제할 수 있다.") + public void deployAndDelete() { + + ABNTestRequestDto requestDto = ABNTestRequestDto.builder() + .name("example") + .domain("so1s.io") + .elements(List.of(ABNTestElementDto.builder().deploymentId(1L).weight(1).build(), + ABNTestElementDto.builder().deploymentId(2L).weight(1).build())) + .build(); + + if (requestDto.getName().isBlank()) { + return; + } + if (requestDto.getDomain().isBlank()) { + return; + } + if (requestDto.getElements().size() < 1) { + return; + } + + var entity = controller.createABNTest(requestDto).getBody().getEntity(); + controller.deleteABNTest(entity.getId()); + + } + +} From 8e5674fd759475bc0c3bfc4042d2c422336916e2 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 20 Nov 2022 16:47:39 +0900 Subject: [PATCH 09/10] chore: reformat code --- .../domain/deployment/controller/DeploymentController.java | 2 +- .../io/so1s/backend/domain/deployment/dto/request/ScaleDto.java | 2 +- .../repository/DeploymentStrategyRepository.java | 2 +- .../model/dto/response/ModelMetadataDeleteResponseDto.java | 2 +- .../domain/model/dto/response/ModelMetadataFindResponseDto.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/so1s/backend/domain/deployment/controller/DeploymentController.java b/src/main/java/io/so1s/backend/domain/deployment/controller/DeploymentController.java index 7739fd8e..12c6806f 100644 --- a/src/main/java/io/so1s/backend/domain/deployment/controller/DeploymentController.java +++ b/src/main/java/io/so1s/backend/domain/deployment/controller/DeploymentController.java @@ -68,7 +68,7 @@ public ResponseEntity updateDeployment( return ResponseEntity.ok(deploymentService.updateDeployment(deploymentRequestDto)); } - + @GetMapping public ResponseEntity> findDeployments() { return ResponseEntity.ok(deploymentService.findDeployments()); diff --git a/src/main/java/io/so1s/backend/domain/deployment/dto/request/ScaleDto.java b/src/main/java/io/so1s/backend/domain/deployment/dto/request/ScaleDto.java index a24d2c2d..33ede1f4 100644 --- a/src/main/java/io/so1s/backend/domain/deployment/dto/request/ScaleDto.java +++ b/src/main/java/io/so1s/backend/domain/deployment/dto/request/ScaleDto.java @@ -21,7 +21,7 @@ public class ScaleDto { @Min(1) private int minReplicas; - + @Min(1) private int maxReplicas; } diff --git a/src/main/java/io/so1s/backend/domain/deployment_strategy/repository/DeploymentStrategyRepository.java b/src/main/java/io/so1s/backend/domain/deployment_strategy/repository/DeploymentStrategyRepository.java index 0be9a9d4..eaee5213 100644 --- a/src/main/java/io/so1s/backend/domain/deployment_strategy/repository/DeploymentStrategyRepository.java +++ b/src/main/java/io/so1s/backend/domain/deployment_strategy/repository/DeploymentStrategyRepository.java @@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface DeploymentStrategyRepository extends JpaRepository { - + Optional findById(Long id); List findAll(); diff --git a/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataDeleteResponseDto.java b/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataDeleteResponseDto.java index 1b4f9801..78eca633 100644 --- a/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataDeleteResponseDto.java +++ b/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataDeleteResponseDto.java @@ -16,5 +16,5 @@ public class ModelMetadataDeleteResponseDto { @Builder.Default private String message = ""; - + } diff --git a/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataFindResponseDto.java b/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataFindResponseDto.java index 8814eb6b..aec00024 100644 --- a/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataFindResponseDto.java +++ b/src/main/java/io/so1s/backend/domain/model/dto/response/ModelMetadataFindResponseDto.java @@ -12,7 +12,7 @@ @NoArgsConstructor @AllArgsConstructor public class ModelMetadataFindResponseDto { - + private Long id; private LocalDateTime age; private String version; From a176639d9dba4eb2e35cbfac24e5780f3915be86 Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Sun, 20 Nov 2022 16:55:33 +0900 Subject: [PATCH 10/10] feat: improve test assertions --- .../unit/test/service/ABNTestServiceTest.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java b/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java index 86655de6..08ff6e55 100644 --- a/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java +++ b/src/test/java/io/so1s/backend/unit/test/service/ABNTestServiceTest.java @@ -1,5 +1,6 @@ package io.so1s.backend.unit.test.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -147,18 +148,19 @@ public void deployAndDelete() { ABNTestElementDto.builder().deploymentId(2L).weight(1).build())) .build(); - if (requestDto.getName().isBlank()) { - return; - } - if (requestDto.getDomain().isBlank()) { - return; - } - if (requestDto.getElements().size() < 1) { - return; - } - - var entity = controller.createABNTest(requestDto).getBody().getEntity(); - controller.deleteABNTest(entity.getId()); + var createResponse = controller.createABNTest(requestDto).getBody(); + var entity = createResponse.getEntity(); + + assertThat(createResponse.getSuccess()).isTrue(); + assertThat(entity).isNotNull(); + assertThat(entity.getName()).isEqualTo("example"); + assertThat(entity.getDomain()).isEqualTo("so1s.io"); + assertThat(entity.getElements()).hasSize(2); + + var deleteResponse = controller.deleteABNTest(createResponse.getEntity().getId()).getBody(); + + assertThat(deleteResponse.getSuccess()).isTrue(); + assertThat(deleteResponse.getMessage()).isNotNull(); }