diff --git a/build.gradle b/build.gradle index 991b8ed9..d14cdd92 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,8 @@ dependencies { // smtp implementation 'org.springframework.boot:spring-boot-starter-mail' + // kubernetes + implementation 'io.fabric8:kubernetes-client:6.10.0' } tasks.named('test') { diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java index 2bc3dfca..1911077c 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/dashboard/service/DashboardService.java @@ -45,7 +45,7 @@ public List getUserServers(Long userId, Status status) { if (resourceGroup != null) { resourceGroupName = resourceGroup.getDescription(); - List nodesInGroup = nodeRepository.findByRsgroupId(resourceGroup.getRsgroupId()); + List nodesInGroup = nodeRepository.findAllByResourceGroup(resourceGroup); if (!nodesInGroup.isEmpty()) { Node representativeNode = nodesInGroup.get(0); cpuCoreCount = representativeNode.getCpuCoreCount(); diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/dto/response/GpuTypeResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/dto/response/GpuTypeResponseDTO.java index de96abf1..c6d6a69d 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/dto/response/GpuTypeResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/dto/response/GpuTypeResponseDTO.java @@ -1,5 +1,6 @@ package DGU_AI_LAB.admin_be.domain.gpus.dto.response; +import DGU_AI_LAB.admin_be.domain.gpus.repository.GpuRepository; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -13,6 +14,8 @@ public record GpuTypeResponseDTO( @Schema(description = "GPU RAM 크기 (GB)", example = "24") Integer ramGb, + String description, + @Schema(description = "GPU가 속한 리소스 그룹명 (같은 스펙 묶음)", example = "RTX 3090") String resourceGroupName, @@ -40,4 +43,13 @@ public static GpuTypeResponseDTO fromQueryResult(Object[] queryResult) { .isAvailable(true) // 현재는 항상 true로 가정 .build(); } + + public static GpuTypeResponseDTO fromSummary(GpuRepository.GpuSummary s) { + return GpuTypeResponseDTO.builder() + .gpuModel(s.getGpuModel()) + .ramGb(s.getRamGb()) + .description(s.getDescription()) + .availableNodes(s.getNodeCount()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/repository/GpuRepository.java b/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/repository/GpuRepository.java index cdccb977..89a5c9ee 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/repository/GpuRepository.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/gpus/repository/GpuRepository.java @@ -10,12 +10,38 @@ @Repository public interface GpuRepository extends JpaRepository { - @Query("SELECT g.gpuModel, g.ramGb, rg.description, COUNT(DISTINCT n.nodeId) " + - "FROM Gpu g JOIN g.node n JOIN ResourceGroup rg ON n.rsgroupId = rg.rsgroupId " + - "GROUP BY g.gpuModel, g.ramGb, rg.description") - List findGpuSummary(); - @Query("SELECT DISTINCT n.cpuCoreCount, n.memorySizeGB " + - "FROM Gpu g JOIN g.node n " + - "WHERE g.gpuModel = :gpuModel") - List findNodeSpecsByGpuModel(String gpuModel); + // GPU 요약 프로젝션 + interface GpuSummary { + String getGpuModel(); + Integer getRamGb(); + String getDescription(); + Long getNodeCount(); + } + + @Query(""" + SELECT g.gpuModel AS gpuModel, + g.ramGb AS ramGb, + rg.description AS description, + COUNT(DISTINCT n.nodeId) AS nodeCount + FROM Gpu g + JOIN g.node n + JOIN n.resourceGroup rg + GROUP BY g.gpuModel, g.ramGb, rg.description + """) + List findGpuSummary(); + + // GPU 모델별 노드 사양 조회 + interface NodeSpec { + Integer getCpuCoreCount(); + Integer getMemorySizeGB(); + } + + @Query(""" + SELECT DISTINCT n.cpuCoreCount AS cpuCoreCount, + n.memorySizeGB AS memorySizeGB + FROM Gpu g + JOIN g.node n + WHERE g.gpuModel = :gpuModel + """) + List findNodeSpecsByGpuModel(String gpuModel); } \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/entity/Node.java b/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/entity/Node.java index 1a7d1fd8..2380dcb0 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/entity/Node.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/entity/Node.java @@ -1,11 +1,13 @@ package DGU_AI_LAB.admin_be.domain.nodes.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import DGU_AI_LAB.admin_be.domain.gpus.entity.Gpu; +import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup; +import jakarta.persistence.*; import lombok.*; +import java.util.LinkedHashSet; +import java.util.Set; + @Entity @Table(name = "nodes") @Getter @@ -22,12 +24,21 @@ public class Node { @Column(name = "node_id", length = 100) private String nodeId; - @Column(name = "rsgroup_id", nullable = false) - private Integer rsgroupId; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rsgroup_id", nullable = false) + private ResourceGroup resourceGroup; @Column(name = "memory_size_GB", nullable = false) private Integer memorySizeGB; @Column(name = "CPU_core_count", nullable = false) private Integer cpuCoreCount; + + @OneToMany(mappedBy = "node", fetch = FetchType.LAZY) + @Builder.Default + private Set gpus = new LinkedHashSet<>(); + + public int getNumberGpu() { + return gpus == null ? 0 : gpus.size(); + } } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/repository/NodeRepository.java b/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/repository/NodeRepository.java index ba745f99..c411ac62 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/repository/NodeRepository.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/nodes/repository/NodeRepository.java @@ -1,6 +1,7 @@ package DGU_AI_LAB.admin_be.domain.nodes.repository; import DGU_AI_LAB.admin_be.domain.nodes.entity.Node; +import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,5 +9,5 @@ @Repository public interface NodeRepository extends JpaRepository { - List findByRsgroupId(Integer rsgroupId); + List findAllByResourceGroup(ResourceGroup resourceGroup); } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/PodController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/PodController.java new file mode 100644 index 00000000..eee233db --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/PodController.java @@ -0,0 +1,51 @@ +package DGU_AI_LAB.admin_be.domain.pod.controller; + +import DGU_AI_LAB.admin_be.domain.pod.controller.docs.PodApi; +import DGU_AI_LAB.admin_be.error.ErrorCode; +import DGU_AI_LAB.admin_be.error.exception.BusinessException; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.*; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import DGU_AI_LAB.admin_be.domain.pod.dto.response.PodResponseDTO; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/pods") +public class PodController implements PodApi { + + private final KubernetesClient client; + + // 전체 pod 목록 조회 + @GetMapping + public List getPods() { + return client.pods() + .inNamespace("default") + .list() + .getItems() + .stream() + .map(pod -> pod.getMetadata().getName()) + .collect(Collectors.toList()); + } + + // 단일 pod 정보 조회 + @GetMapping("/{podName}") + public PodResponseDTO getPodDetail(@PathVariable String podName) { + Pod pod = client.pods() + .inNamespace("default") + .withName(podName) + .get(); + + if (pod == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + + return PodResponseDTO.fromEntity(pod); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/docs/PodApi.java b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/docs/PodApi.java new file mode 100644 index 00000000..bb168cc4 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/controller/docs/PodApi.java @@ -0,0 +1,52 @@ +package DGU_AI_LAB.admin_be.domain.pod.controller.docs; + +import DGU_AI_LAB.admin_be.domain.pod.dto.response.PodResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; + +@Tag(name = "Kubernetes Pods", description = "쿠버네티스 Pod 조회 API") +public interface PodApi { + + @Operation( + summary = "전체 Pod 목록 조회" + ) + @ApiResponse( + responseCode = "200", + description = "조회 성공" + ) + @ApiResponse( + responseCode = "500", + description = "서버 오류", + content = @Content + ) + List getPods(); + + @Operation( + summary = "단일 pod 정보 조회" + ) + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = PodResponseDTO.class)) + ) + @ApiResponse( + responseCode = "404", + description = "해당 이름의 Pod를 찾을 수 없음", + content = @Content + ) + @ApiResponse( + responseCode = "500", + description = "서버 오류", + content = @Content + ) + PodResponseDTO getPodDetail( + @Parameter(description = "조회할 Pod의 이름") + String podName + ); +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/pod/dto/response/PodResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/dto/response/PodResponseDTO.java new file mode 100644 index 00000000..f3eaa4e8 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/pod/dto/response/PodResponseDTO.java @@ -0,0 +1,71 @@ +package DGU_AI_LAB.admin_be.domain.pod.dto.response; + +import io.fabric8.kubernetes.api.model.Pod; +import lombok.Builder; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Builder +public record PodResponseDTO( + String name, + String namespace, + String status, + String creationTimestamp, + Map labels, + Map annotations, + List containers, + List volumes, + String hostIP, + String nodeName +) { + + @Builder + public record ContainerDTO( + String name, + String image + ) { + } + + @Builder + public record VolumeDTO( + String name + ) { + } + + public static PodResponseDTO fromEntity(Pod pod) { + List volumes = pod.getSpec() != null && pod.getSpec().getVolumes() != null + ? pod.getSpec().getVolumes().stream() + .map(volume -> VolumeDTO.builder() + .name(volume.getName()) + .build()) + .collect(Collectors.toList()) + : List.of(); + + List containers = pod.getSpec() != null && pod.getSpec().getContainers() != null + ? pod.getSpec().getContainers().stream() + .map(container -> ContainerDTO.builder() + .name(container.getName()) + .image(container.getImage()) + .build()) + .collect(Collectors.toList()) + : List.of(); + + String hostIP = pod.getStatus() != null ? pod.getStatus().getHostIP() : null; + String nodeName = pod.getSpec() != null ? pod.getSpec().getNodeName() : null; + + return PodResponseDTO.builder() + .name(pod.getMetadata().getName()) + .namespace(pod.getMetadata().getNamespace()) + .status(pod.getStatus() != null ? pod.getStatus().getPhase() : "Unknown") + .creationTimestamp(pod.getMetadata().getCreationTimestamp()) + .labels(pod.getMetadata().getLabels()) + .annotations(pod.getMetadata().getAnnotations()) + .containers(containers) + .volumes(volumes) + .hostIP(hostIP) + .nodeName(nodeName) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/AcceptInfoController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/AcceptInfoController.java new file mode 100644 index 00000000..9bb85eda --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/AcceptInfoController.java @@ -0,0 +1,24 @@ +package DGU_AI_LAB.admin_be.domain.requests.controller; + +import DGU_AI_LAB.admin_be.domain.requests.controller.docs.AcceptInfoApi; +import DGU_AI_LAB.admin_be.domain.requests.dto.response.AcceptInfoResponseDTO; +import DGU_AI_LAB.admin_be.domain.requests.service.RequestService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/acceptinfo") +public class AcceptInfoController implements AcceptInfoApi { + private final RequestService requestService; + + @GetMapping("/{username}") + public ResponseEntity getAcceptInfo(@PathVariable String username) { + AcceptInfoResponseDTO response = requestService.getAcceptInfo(username); + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/RequestController.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/RequestController.java index 82a28cd2..b914e269 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/RequestController.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/RequestController.java @@ -36,6 +36,16 @@ public ResponseEntity> getMyRequests( return ResponseEntity.ok(requestService.getRequestsByUserId(user.getUserId())); } + @GetMapping("/check-username") + public ResponseEntity checkUbuntuUsername(@RequestParam String username) { + boolean available = requestService.isUbuntuUsernameAvailable(username); + return ResponseEntity.ok().body( + java.util.Map.of( + "available", available + ) + ); + } + /*@PostMapping("/modify") public ResponseEntity requestModification(@RequestBody ModifyRequestDTO dto) { requestService.requestModification(dto); diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/docs/AcceptInfoApi.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/docs/AcceptInfoApi.java new file mode 100644 index 00000000..e9b168b5 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/controller/docs/AcceptInfoApi.java @@ -0,0 +1,33 @@ +package DGU_AI_LAB.admin_be.domain.requests.controller.docs; + +import DGU_AI_LAB.admin_be.domain.requests.dto.response.AcceptInfoResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import org.springframework.http.ResponseEntity; + +@Tag(name = "Config Server용 승인 정보 관리", description = "Ubuntu username별 승인 정보 조회 API") +public interface AcceptInfoApi { + + @Operation( + summary = "사용자 승인 정보 조회", + description = "Ubuntu username으로 승인된 서버 사용 신청 정보를 조회합니다." + ) + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = AcceptInfoResponseDTO.class)) + ) + @ApiResponse( + responseCode = "404", + description = "해당 username의 승인 정보를 찾을 수 없음", + content = @Content + ) + ResponseEntity getAcceptInfo( + @Parameter(description = "Ubuntu username") + String username + ); +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/PvcRequest.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/PvcRequest.java new file mode 100644 index 00000000..652ec8f5 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/PvcRequest.java @@ -0,0 +1,6 @@ +package DGU_AI_LAB.admin_be.domain.requests.dto.request; + +public record PvcRequest ( + String username, + Long storage +) {} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/SaveRequestRequestDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/SaveRequestRequestDTO.java index a6982506..d58ab209 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/SaveRequestRequestDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/request/SaveRequestRequestDTO.java @@ -5,7 +5,6 @@ import DGU_AI_LAB.admin_be.domain.requests.entity.Request; import DGU_AI_LAB.admin_be.domain.requests.entity.Status; import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup; -import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; import DGU_AI_LAB.admin_be.domain.users.entity.User; import DGU_AI_LAB.admin_be.error.ErrorCode; import DGU_AI_LAB.admin_be.error.exception.BusinessException; @@ -38,8 +37,6 @@ public record SaveRequestRequestDTO( Map formAnswers, LocalDateTime expiresAt, - - @Schema(description = "gid", example = "10001") Set ubuntuGids ) { public Request toEntity( @@ -70,11 +67,6 @@ public Request toEntity( .status(Status.PENDING) .build(); - // 2) 그룹 연결은 addGroup()으로 — 중간 엔티티(request_groups) 생성 - if (groups != null && !groups.isEmpty()) { - groups.forEach(req::addGroup); - } - return req; } } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/AcceptInfoResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/AcceptInfoResponseDTO.java new file mode 100644 index 00000000..a4fda9dd --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/AcceptInfoResponseDTO.java @@ -0,0 +1,57 @@ +package DGU_AI_LAB.admin_be.domain.requests.dto.response; + +import DGU_AI_LAB.admin_be.domain.nodes.entity.Node; +import DGU_AI_LAB.admin_be.domain.requests.entity.Request; +import lombok.Builder; +import java.util.List; + +@Builder +public record AcceptInfoResponseDTO( + String username, + String image, + Long uid, + List gid, + Long volume_size, + Boolean gpu_required, + String gpu_group, + String server_type, + List gpu_nodes +) { + @Builder + public record NodeDTO( + String node_name, + String cpu_limit, + String memory_limit, + Integer num_gpu + ) {} + + public static AcceptInfoResponseDTO fromEntity(Request request, List nodes) { + var image = request.getContainerImage(); + var group = request.getResourceGroup(); + + List nodeDTOList = nodes.stream() + .map(node -> NodeDTO.builder() + .node_name(node.getNodeId()) + .cpu_limit(node.getCpuCoreCount() * 1000 + "m") + .memory_limit(node.getMemorySizeGB() * 1024 + "Mi") + .num_gpu(node.getNumberGpu()) + .build() + ).toList(); + + return AcceptInfoResponseDTO.builder() + .username(request.getUbuntuUsername()) + .image(image.getImageName() + ":" + image.getImageVersion()) + .uid(request.getUbuntuUid().getIdValue()) + .gid( + request.getRequestGroups().stream() + .map(rg -> rg.getGroup().getUbuntuGid()) + .toList() + ) + .volume_size(request.getVolumeSizeGiB()) + .gpu_required(true) + .gpu_group(group.getDescription()) + .server_type(group.getServerName()) + .gpu_nodes(nodeDTOList) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java index 74acf9d8..b1cf14d5 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/SaveRequestResponseDTO.java @@ -6,6 +6,7 @@ import lombok.Builder; import java.time.LocalDateTime; +import java.util.List; @Builder public record SaveRequestResponseDTO( @@ -15,6 +16,7 @@ public record SaveRequestResponseDTO( String imageVersion, String ubuntuUsername, Long ubuntuUid, + List ubuntuGids, Long volumeSizeByte, String usagePurpose, @JsonRawValue String formAnswers, @@ -30,6 +32,14 @@ public static SaveRequestResponseDTO fromEntity(Request request) { .imageName(request.getContainerImage().getImageName()) .imageVersion(request.getContainerImage().getImageVersion()) .ubuntuUsername(request.getUbuntuUsername()) + .ubuntuUid(request.getUbuntuUid() != null + ? request.getUbuntuUid().getIdValue() + : null) + .ubuntuGids( + request.getRequestGroups().stream() + .map(rg -> rg.getGroup().getUbuntuGid()) + .toList() + ) .volumeSizeByte(request.getVolumeSizeGiB()) .usagePurpose(request.getUsagePurpose()) .formAnswers(request.getFormAnswers()) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java index 7c9d1808..ad55a06b 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/dto/response/UserServerResponseDTO.java @@ -16,12 +16,11 @@ public record UserServerResponseDTO( String serverAddress, @Schema(description = "서버 사용 만료일", example = "2025-12-31T23:59:59") LocalDateTime expiresAt, - @Schema(description = "할당된 볼륨 크기 (GB)", example = "100") - Long volumeSizeGB, - @Schema(description = "사용된 CUDA 버전", example = "12.0") - String cudaVersion, + @Schema(description = "할당된 볼륨 크기 (GiB)", example = "100") + Long volumeSizeGiB, @Schema(description = "할당된 CPU 코어 수", example = "8") Integer cpuCoreCount, + // TODO: 용도가 무엇인지 모르겠으나 GiB로 통일하는 것이 좋아보임. @Schema(description = "할당된 메모리 크기 (GB)", example = "32") Integer memoryGB, @Schema(description = "할당된 리소스 그룹명 (GPU 스펙 묶음)", example = "RTX 3090 D6 24GB 그룹") @@ -34,8 +33,7 @@ public static UserServerResponseDTO fromEntity(Request request, String serverAdd .requestId(request.getRequestId()) .serverAddress(serverAddress) .expiresAt(request.getExpiresAt()) - .volumeSizeGB(request.getVolumeSizeByte() / (1024L * 1024 * 1024)) - .cudaVersion(request.getCudaVersion()) + .volumeSizeGiB(request.getVolumeSizeGiB() / (1024L * 1024 * 1024)) .cpuCoreCount(cpuCoreCount) .memoryGB(memoryGB) .resourceGroupName(resourceGroupName) diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Request.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Request.java index 23c75835..6d69df2b 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Request.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/Request.java @@ -5,6 +5,8 @@ import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup; import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; import DGU_AI_LAB.admin_be.domain.users.entity.User; +import DGU_AI_LAB.admin_be.error.ErrorCode; +import DGU_AI_LAB.admin_be.error.exception.BusinessException; import DGU_AI_LAB.admin_be.global.common.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; @@ -24,7 +26,7 @@ public class Request extends BaseTimeEntity { @Column(name = "request_id") private Long requestId; - @Column(name = "ubuntu_username", nullable = false, length = 100) + @Column(name = "ubuntu_username", nullable = false, length = 100, unique = true) private String ubuntuUsername; @Column(name = "ubuntu_password", nullable = false) @@ -128,14 +130,26 @@ public void reject(String comment) { this.adminComment = "변경 요청 거절: " + reason; }*/ + public void assignUbuntuUid(UsedId uid) { + this.ubuntuUid = uid; + } + public void addGroup(Group group) { + Long rid = this.getRequestId(); + if (rid == null) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + RequestGroup rg = RequestGroup.builder() + .id(new RequestGroupId(rid, group.getUbuntuGid())) .request(this) .group(group) .build(); + this.requestGroups.add(rg); } + public void removeGroup(Long ubuntuGid) { this.requestGroups.removeIf(rg -> rg.getGroup().getUbuntuGid().equals(ubuntuGid)); } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroup.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroup.java index a4df620a..078a90e8 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroup.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroup.java @@ -8,6 +8,7 @@ @Entity @Table(name = "request_groups") +@Access(AccessType.FIELD) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroupId.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroupId.java index a9a49847..68637640 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroupId.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/entity/RequestGroupId.java @@ -1,5 +1,7 @@ package DGU_AI_LAB.admin_be.domain.requests.entity; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.*; @@ -7,10 +9,10 @@ import java.io.Serializable; @Embeddable +@Access(AccessType.FIELD) @Getter @NoArgsConstructor @AllArgsConstructor -@Builder @EqualsAndHashCode public class RequestGroupId implements Serializable { diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/repository/RequestRepository.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/repository/RequestRepository.java index 694fd8fa..5c190cc8 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/repository/RequestRepository.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/repository/RequestRepository.java @@ -12,8 +12,12 @@ @Repository public interface RequestRepository extends JpaRepository { List findAllByUser(User user); + Optional findByUbuntuUsername(String username); List findAllByUser_UserId(Long userId); List findAllByStatus(Status status); Optional findByUbuntuUsernameAndUbuntuPassword(String username, String passwordBase64); List findByUserUserIdAndStatus(Long userId, Status status); + Optional findTopByUbuntuUsernameAndUbuntuUidIsNotNullOrderByApprovedAtDesc(String ubuntuUsername); + + boolean existsByUbuntuUsername(String ubuntuUsername); } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java index 174908fc..f6d0137a 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/requests/service/RequestService.java @@ -4,7 +4,10 @@ import DGU_AI_LAB.admin_be.domain.containerImage.repository.ContainerImageRepository; import DGU_AI_LAB.admin_be.domain.groups.entity.Group; import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; +import DGU_AI_LAB.admin_be.domain.nodes.entity.Node; +import DGU_AI_LAB.admin_be.domain.nodes.repository.NodeRepository; import DGU_AI_LAB.admin_be.domain.requests.dto.request.*; +import DGU_AI_LAB.admin_be.domain.requests.dto.response.AcceptInfoResponseDTO; import DGU_AI_LAB.admin_be.domain.requests.dto.response.ContainerInfoDTO; import DGU_AI_LAB.admin_be.domain.requests.dto.response.ResourceUsageDTO; import DGU_AI_LAB.admin_be.domain.requests.dto.response.SaveRequestResponseDTO; @@ -15,20 +18,29 @@ import DGU_AI_LAB.admin_be.domain.resourceGroups.repository.ResourceGroupRepository; import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; import DGU_AI_LAB.admin_be.domain.usedIds.repository.UsedIdRepository; +import DGU_AI_LAB.admin_be.domain.usedIds.service.IdAllocationService; import DGU_AI_LAB.admin_be.domain.users.entity.User; import DGU_AI_LAB.admin_be.domain.users.repository.UserRepository; import DGU_AI_LAB.admin_be.error.ErrorCode; import DGU_AI_LAB.admin_be.error.exception.BusinessException; import DGU_AI_LAB.admin_be.global.util.PasswordUtil; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; import java.util.HashSet; import java.util.List; import java.util.Set; +@Slf4j @Service @RequiredArgsConstructor public class RequestService { @@ -40,6 +52,12 @@ public class RequestService { private final ResourceGroupRepository resourceGroupRepository; private final UsedIdRepository usedIdRepository; private final PasswordEncoder passwordEncoder; + private final RestTemplate restTemplate; + private final IdAllocationService idAllocationService; + private final NodeRepository nodeRepository; + + @Value("${pvc.base-url}") + private String pvcBaseUrl; /** 신청 생성 */ @Transactional @@ -53,24 +71,32 @@ public SaveRequestResponseDTO createRequest(Long userId, SaveRequestRequestDTO d ContainerImage img = containerImageRepository.findById(dto.imageId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND)); - // 수정 필요 - /*UsedId usedId = usedIdRepository.findById(dto.ubuntuUid()) - .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND));*/ - String ubuntuPassword = PasswordUtil.encodePassword(dto.ubuntuPassword()); - Set groups = dto.ubuntuGids() == null || dto.ubuntuGids().isEmpty() - ? Set.of() - : new HashSet<>(groupRepository.findAllByUbuntuGidIn(dto.ubuntuGids())); + Request req = dto.toEntity( + user, + rg, + img, + java.util.Collections.emptySet(), + ubuntuPassword + ); - if (groups.size() != (dto.ubuntuGids() == null ? 0 : dto.ubuntuGids().size())) { - throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); - } + req = requestRepository.save(req); + requestRepository.flush(); - Request saved = requestRepository.save(dto.toEntity(user, rg, img, groups, ubuntuPassword)); - return SaveRequestResponseDTO.fromEntity(saved); - } + if (dto.ubuntuGids() != null && !dto.ubuntuGids().isEmpty()) { + Set found = new java.util.HashSet<>(groupRepository.findAllByUbuntuGidIn(dto.ubuntuGids())); + if (found.size() != dto.ubuntuGids().size()) { + throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND); + } + + for (Group g : found) { + req.addGroup(g); + } + } + return SaveRequestResponseDTO.fromEntity(req); + } /** 신청 승인 */ @Transactional @@ -82,6 +108,18 @@ public SaveRequestResponseDTO approveRequest(ApproveRequestDTO dto) { throw new BusinessException(ErrorCode.INVALID_REQUEST_STATUS); } + // UID 할당 + var allocation = idAllocationService.allocateFor(request); + + request.assignUbuntuUid(allocation.getUid()); + + boolean alreadyLinked = request.getRequestGroups().stream() + .anyMatch(rg -> rg.getGroup().getUbuntuGid() + .equals(allocation.getPrimaryGroup().getUbuntuGid())); + if (!alreadyLinked) { + request.addGroup(allocation.getPrimaryGroup()); + } + ContainerImage image = containerImageRepository.findById(dto.imageId()) .orElseThrow(() -> new BusinessException(ErrorCode.RESOURCE_NOT_FOUND)); @@ -95,6 +133,31 @@ public SaveRequestResponseDTO approveRequest(ApproveRequestDTO dto) { dto.expiresAt(), dto.adminComment() ); + + requestRepository.flush(); + + // pvc post + String url = pvcBaseUrl + "/pvc"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + PvcRequest body = new PvcRequest( + request.getUbuntuUsername(), + request.getVolumeSizeGiB() + ); + + HttpEntity entity = new HttpEntity<>(body, headers); + + try { + ResponseEntity res = restTemplate.postForEntity(url, entity, Void.class); + if (!res.getStatusCode().is2xxSuccessful()) { + throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); + } + } catch (Exception ex) { + throw new BusinessException(ErrorCode.EXTERNAL_API_FAILED); + } + return SaveRequestResponseDTO.fromEntity(request); } @@ -133,6 +196,23 @@ public List getRequestsByUserId(Long userId) { .toList(); } + /** ubuntu username 중복 검사 */ + @Transactional(readOnly = true) + public boolean isUbuntuUsernameAvailable(String username) { + return !requestRepository.existsByUbuntuUsername(username); + } + + /** config server용 acceptinfo */ + public AcceptInfoResponseDTO getAcceptInfo(String username) { + Request request = requestRepository.findByUbuntuUsername(username) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_APPROVAL_NOT_FOUND)); + + ResourceGroup group = request.getResourceGroup(); + List nodes = nodeRepository.findAllByResourceGroup(group); + + return AcceptInfoResponseDTO.fromEntity(request, nodes); + } + /** 변경 요청 */ /*@Transactional public void requestModification(ModifyRequestDTO dto) { diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/entity/ResourceGroup.java b/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/entity/ResourceGroup.java index 833ee470..c9161e05 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/entity/ResourceGroup.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/entity/ResourceGroup.java @@ -21,4 +21,7 @@ public class ResourceGroup { @Column(name = "description", length = 500) private String description; + + @Column(name = "server_name", length = 300) + private String serverName; } diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/service/ResourceGroupService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/service/ResourceGroupService.java index 9a3650a4..2d9283e1 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/service/ResourceGroupService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/resourceGroups/service/ResourceGroupService.java @@ -27,16 +27,24 @@ public class ResourceGroupService { public List getGpuTypeResources() { log.info("[getGpuTypeResources] GPU 기종별 리소스 정보 조회 시작"); - List gpuSummaries = gpuRepository.findGpuSummary(); + List gpuSummaries = gpuRepository.findGpuSummary(); + + for (GpuRepository.GpuSummary summary : gpuSummaries) { + System.out.println(summary.getGpuModel()); + System.out.println(summary.getRamGb()); + System.out.println(summary.getDescription()); + System.out.println(summary.getNodeCount()); + } if (gpuSummaries.isEmpty()) { log.warn("[getGpuTypeResources] 조회된 GPU 기종별 리소스가 없습니다."); throw new BusinessException(ErrorCode.NO_AVAILABLE_RESOURCES); } - List response = gpuSummaries.stream() - .map(GpuTypeResponseDTO::fromQueryResult) - .collect(Collectors.toList()); + var summaries = gpuRepository.findGpuSummary(); // List + var response = summaries.stream() + .map(GpuTypeResponseDTO::fromSummary) + .toList(); log.info("[getGpuTypeResources] GPU 기종별 리소스 정보 조회 완료. {}개 기종", response.size()); return response; diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/repository/UsedIdRepository.java b/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/repository/UsedIdRepository.java index a8a67561..6fe93fbb 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/repository/UsedIdRepository.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/repository/UsedIdRepository.java @@ -2,7 +2,11 @@ import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; -public interface UsedIdRepository extends JpaRepository { -} +import java.util.Optional; +public interface UsedIdRepository extends JpaRepository { + @Query("SELECT MAX(u.idValue) FROM UsedId u") + Optional findMaxIdValue(); +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/service/IdAllocationService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/service/IdAllocationService.java new file mode 100644 index 00000000..c8ccd465 --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/usedIds/service/IdAllocationService.java @@ -0,0 +1,85 @@ +package DGU_AI_LAB.admin_be.domain.usedIds.service; + +import DGU_AI_LAB.admin_be.domain.groups.entity.Group; +import DGU_AI_LAB.admin_be.domain.groups.repository.GroupRepository; +import DGU_AI_LAB.admin_be.domain.requests.entity.Request; +import DGU_AI_LAB.admin_be.domain.requests.repository.RequestRepository; +import DGU_AI_LAB.admin_be.domain.usedIds.entity.UsedId; +import DGU_AI_LAB.admin_be.domain.usedIds.repository.UsedIdRepository; +import DGU_AI_LAB.admin_be.error.ErrorCode; +import DGU_AI_LAB.admin_be.error.exception.BusinessException; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class IdAllocationService { + + private static final long UID_BASE = 10_000L; + private static final int MAX_RETRY = 5; + + private final UsedIdRepository usedIdRepository; + private final GroupRepository groupRepository; + private final RequestRepository requestRepository; + + @Getter + @AllArgsConstructor + public static class AllocationResult { + private final UsedId uid; + private final Group primaryGroup; + } + + @Transactional + public AllocationResult allocateFor(Request request) { + final String username = request.getUbuntuUsername(); + + UsedId uid = findReusableUidByUbuntuUsername(username) + .orElseGet(this::allocateNewUid); + + Group primaryGroup = groupRepository.findById(uid.getIdValue()) + .orElseGet(() -> createGroupWithSameId(username, uid.getIdValue())); + + return new AllocationResult(uid, primaryGroup); + } + + private Optional findReusableUidByUbuntuUsername(String ubuntuUsername) { + return requestRepository + .findTopByUbuntuUsernameAndUbuntuUidIsNotNullOrderByApprovedAtDesc(ubuntuUsername) + .map(Request::getUbuntuUid); + } + + // 새 UID 생성 + private UsedId allocateNewUid() { + for (int i = 0; i < MAX_RETRY; i++) { + long currentMax = usedIdRepository.findMaxIdValue().orElse(UID_BASE - 1L); + long candidate = Math.max(UID_BASE, currentMax + 1); + try { + return usedIdRepository.saveAndFlush(UsedId.builder().idValue(candidate).build()); + } catch (DataIntegrityViolationException ignore) {} + } + throw new BusinessException(ErrorCode.UID_ALLOCATION_FAILED); + } + + private Group createGroupWithSameId(String username, long uidValue) { + Optional existing = groupRepository.findById(uidValue); + if (existing.isPresent()) return existing.get(); + + UsedId gidUsedId = usedIdRepository.findById(uidValue) + .orElseGet(() -> usedIdRepository.saveAndFlush( + UsedId.builder().idValue(uidValue).build() + )); + + Group group = Group.builder() + .groupName(username) + .usedId(gidUsedId) + .build(); + + return groupRepository.saveAndFlush(group); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java b/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java index 1bf7fd08..296072aa 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java +++ b/src/main/java/DGU_AI_LAB/admin_be/domain/users/service/UserService.java @@ -6,7 +6,6 @@ import DGU_AI_LAB.admin_be.domain.users.dto.request.UserAuthRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.request.PasswordUpdateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.request.PhoneUpdateRequestDTO; -import DGU_AI_LAB.admin_be.domain.users.dto.request.UserCreateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.request.UserUpdateRequestDTO; import DGU_AI_LAB.admin_be.domain.users.dto.response.MyInfoResponseDTO; import DGU_AI_LAB.admin_be.domain.users.dto.response.UserAuthResponseDTO; @@ -113,7 +112,6 @@ public UserAuthResponseDTO userAuth(UserAuthRequestDTO dto) { return new UserAuthResponseDTO(true, request.getUbuntuUsername()); } -} /** * 사용자 비밀번호 변경 diff --git a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java index 7c0365ea..74177a01 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java +++ b/src/main/java/DGU_AI_LAB/admin_be/error/ErrorCode.java @@ -67,6 +67,7 @@ public enum ErrorCode { * 502 Bad Gateway */ SLACK_DM_CHANNEL_FAILED(HttpStatus.BAD_GATEWAY, "Slack DM 채널 열기를 실패하였습니다."), + EXTERNAL_API_FAILED(HttpStatus.BAD_GATEWAY, "외부 API 호출에 실패했습니다."), /** * 503 Service Unavailable @@ -84,7 +85,7 @@ public enum ErrorCode { INVALID_LOGIN_INFO(HttpStatus.BAD_REQUEST, "잘못된 로그인 입력값입니다."), INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "올바르지 않은 인증 코드입니다."), GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "지정된 그룹을 찾을 수 없습니다."), - UID_ALLOCATION_FAILED(HttpStatus.BAD_REQUEST, "UID를 할당하는 데 실패했습니다."), // 이 부분 고민 + UID_ALLOCATION_FAILED(HttpStatus.BAD_REQUEST, "UID를 할당에 실패했습니다."), SLACK_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "Slack 사용자를 찾을 수 없습니다."), SLACK_USER_EMAIL_NOT_MATCH(HttpStatus.NOT_FOUND, "이메일이 일치하는 Slack 사용자를 찾을 수 없습니다."), diff --git a/src/main/java/DGU_AI_LAB/admin_be/global/config/KubernetesConfig.java b/src/main/java/DGU_AI_LAB/admin_be/global/config/KubernetesConfig.java index 0610ee20..766873c6 100644 --- a/src/main/java/DGU_AI_LAB/admin_be/global/config/KubernetesConfig.java +++ b/src/main/java/DGU_AI_LAB/admin_be/global/config/KubernetesConfig.java @@ -1,14 +1,14 @@ -//package DGU_AI_LAB.admin_be.global.config; -// -//import io.fabric8.kubernetes.client.KubernetesClient; -//import io.fabric8.kubernetes.client.KubernetesClientBuilder; -//import org.springframework.context.annotation.Bean; -//import org.springframework.context.annotation.Configuration; -// -//@Configuration -//public class KubernetesConfig { -// @Bean -// public KubernetesClient kubernetesClient() { -// return new KubernetesClientBuilder().build(); -// } -//} \ No newline at end of file +package DGU_AI_LAB.admin_be.global.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class KubernetesConfig { + @Bean + public KubernetesClient kubernetesClient() { + return new KubernetesClientBuilder().build(); + } +} \ No newline at end of file diff --git a/src/main/java/DGU_AI_LAB/admin_be/global/config/RestTemplateConfig.java b/src/main/java/DGU_AI_LAB/admin_be/global/config/RestTemplateConfig.java new file mode 100644 index 00000000..f92551dc --- /dev/null +++ b/src/main/java/DGU_AI_LAB/admin_be/global/config/RestTemplateConfig.java @@ -0,0 +1,32 @@ +package DGU_AI_LAB.admin_be.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.net.http.HttpClient; +import java.time.Duration; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate( + RestTemplateBuilder builder, + @Value("${pvc.timeout-seconds:5}") int timeoutSeconds + ) { + HttpClient httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(timeoutSeconds)) + .build(); + + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient); + requestFactory.setReadTimeout(Duration.ofSeconds(timeoutSeconds)); + + return builder + .requestFactory(() -> requestFactory) + .build(); + } +} \ No newline at end of file