Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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.portRequests.entity.PortRequests;
import DGU_AI_LAB.admin_be.domain.requests.entity.Request;
import io.swagger.v3.oas.annotations.media.Schema;
Expand All @@ -15,15 +16,37 @@ public record AcceptInfoResponseDTO(
String username,
@Schema(description = "컨테이너 이미지 (이름:버전)", example = "cuda:11.8")
String image,
@Schema(description = "Ubuntu GID 목록", example = "[1005, 1006]")
List<Long> gid,
@Schema(description = "그룹 목록 (GID + 그룹명)")
List<GroupDTO> groups,
@Schema(description = "볼륨 크기 (GiB)", example = "20")
Long volume_size,
@Schema(description = "GPU 필요 여부", example = "true")
Boolean gpu_required,
@Schema(description = "GPU 노드 목록")
List<GpuNodeDTO> gpu_nodes,
@Schema(description = "추가 포트 목록")
List<AdditionalPortDTO> additional_ports
) {
@Schema(description = "그룹 정보")
@Builder
public record GroupDTO(
@Schema(description = "Ubuntu GID", example = "10004")
Long gid,
@Schema(description = "그룹명", example = "ailab")
String name
) {}

@Schema(description = "GPU 노드 정보")
@Builder
public record GpuNodeDTO(
@Schema(description = "노드명", example = "farm2")
String node_name,
@Schema(description = "GPU 수", example = "2")
int num_gpu,
@Schema(description = "CPU 제한 (k8s 포맷)", example = "4000m")
String cpu_limit,
@Schema(description = "메모리 제한 (k8s 포맷)", example = "8192Mi")
String memory_limit
) {}

@Schema(description = "추가 포트 정보")
@Builder
public record AdditionalPortDTO(
Expand All @@ -33,26 +56,38 @@ public record AdditionalPortDTO(
String usage_purpose
) {}

public static AcceptInfoResponseDTO fromEntity(Request request, List<PortRequests> portRequests) {
public static AcceptInfoResponseDTO fromEntity(Request request, List<PortRequests> portRequests, List<Node> nodes) {
var image = request.getContainerImage();

List<GroupDTO> groupDTOList = request.getRequestGroups().stream()
.map(rg -> GroupDTO.builder()
.gid(rg.getGroup().getUbuntuGid())
.name(rg.getGroup().getGroupName())
.build())
.toList();

List<GpuNodeDTO> gpuNodeDTOList = nodes.stream()
.map(node -> GpuNodeDTO.builder()
.node_name(node.getNodeId())
.num_gpu(node.getNumberGpu())
.cpu_limit(node.getCpuCoreCount() * 1000 + "m")
.memory_limit(node.getMemorySizeGB() * 1024 + "Mi")
.build())
.toList();

List<AdditionalPortDTO> additionalPortDTOList = portRequests.stream()
.map(portRequest -> AdditionalPortDTO.builder()
.internal_port(portRequest.getInternalPort())
.usage_purpose(portRequest.getUsagePurpose())
.build()
).toList();
.build())
.toList();

return AcceptInfoResponseDTO.builder()
.username(request.getUbuntuUsername())
.image(image.getImageName() + ":" + image.getImageVersion())
.gid(
request.getRequestGroups().stream()
.map(rg -> rg.getGroup().getUbuntuGid())
.toList()
)
.groups(groupDTOList)
.volume_size(request.getVolumeSizeGiB())
.gpu_required(true)
.gpu_nodes(gpuNodeDTOList)
.additional_ports(additionalPortDTOList)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package DGU_AI_LAB.admin_be.domain.requests.service;

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.portRequests.entity.PortRequests;
import DGU_AI_LAB.admin_be.domain.portRequests.repository.PortRequestRepository;
import DGU_AI_LAB.admin_be.domain.requests.dto.response.AcceptInfoResponseDTO;
Expand All @@ -21,6 +23,7 @@ public class ConfigRequestService {

private final RequestRepository requestRepository;
private final PortRequestRepository portRequestRepository;
private final NodeRepository nodeRepository;

/** ubuntu username 중복 검사 */
@Transactional(readOnly = true)
Expand All @@ -29,10 +32,10 @@ public boolean isUbuntuUsernameAvailable(String username) {
}

/** config server용 acceptinfo */
@Transactional(readOnly = true)
public AcceptInfoResponseDTO getAcceptInfo(String username) {
log.info("사용자 승인 정보 조회를 시작합니다. username: {}", username);

// 사용자 요청 정보 조회
Request request = requestRepository.findByUbuntuUsername(username)
.orElseThrow(() -> {
log.warn("사용자 '{}'에 대한 승인 정보가 존재하지 않습니다.", username);
Expand All @@ -41,12 +44,13 @@ public AcceptInfoResponseDTO getAcceptInfo(String username) {

log.debug("사용자 '{}'의 요청 정보를 성공적으로 찾았습니다. 요청 ID: {}", username, request.getRequestId());

// 포트 요청 정보 조회
List<PortRequests> portRequests = portRequestRepository.findByRequestRequestId(request.getRequestId());
log.debug("요청 '{}'에 속한 포트 요청 {}개를 조회했습니다.", request.getRequestId(), portRequests.size());

// 응답 DTO 생성 및 반환
AcceptInfoResponseDTO response = AcceptInfoResponseDTO.fromEntity(request, portRequests);
List<Node> nodes = nodeRepository.findAllByResourceGroup(request.getResourceGroup());
log.debug("리소스 그룹 소속 노드 {}개를 조회했습니다.", nodes.size());

AcceptInfoResponseDTO response = AcceptInfoResponseDTO.fromEntity(request, portRequests, nodes);
log.info("사용자 '{}'에 대한 AcceptInfoResponseDTO 생성을 완료했습니다.", username);

return response;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package DGU_AI_LAB.admin_be.domain.requests.service;

import DGU_AI_LAB.admin_be.domain.containerImage.entity.ContainerImage;
import DGU_AI_LAB.admin_be.domain.groups.entity.Group;
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.portRequests.entity.PortRequests;
import DGU_AI_LAB.admin_be.domain.portRequests.repository.PortRequestRepository;
import DGU_AI_LAB.admin_be.domain.requests.dto.response.AcceptInfoResponseDTO;
import DGU_AI_LAB.admin_be.domain.requests.entity.Request;
import DGU_AI_LAB.admin_be.domain.requests.entity.RequestGroup;
import DGU_AI_LAB.admin_be.domain.requests.repository.RequestRepository;
import DGU_AI_LAB.admin_be.domain.resourceGroups.entity.ResourceGroup;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -16,6 +21,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
Expand All @@ -26,19 +32,130 @@ class ConfigRequestServiceTest {

@Mock private RequestRepository requestRepository;
@Mock private PortRequestRepository portRequestRepository;
@Mock private NodeRepository nodeRepository;

private ConfigRequestService service;

@BeforeEach
void setUp() {
service = new ConfigRequestService(requestRepository, portRequestRepository);
service = new ConfigRequestService(requestRepository, portRequestRepository, nodeRepository);
}

private Request mockRequest(long requestId, String username, ResourceGroup resourceGroup) {
ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("cuda");
when(image.getImageVersion()).thenReturn("11.8");

Request request = mock(Request.class);
when(request.getRequestId()).thenReturn(requestId);
when(request.getUbuntuUsername()).thenReturn(username);
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>());
when(request.getVolumeSizeGiB()).thenReturn(20L);
when(request.getResourceGroup()).thenReturn(resourceGroup);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(portRequestRepository.findByRequestRequestId(requestId)).thenReturn(List.of());
when(nodeRepository.findAllByResourceGroup(resourceGroup)).thenReturn(List.of());

return request;
}

@Test
@DisplayName("groups 필드는 GID와 그룹명을 함께 반환한다")
void getAcceptInfo_returnsGroupsWithGidAndName() {
// Given
String username = "testuser";
ResourceGroup resourceGroup = mock(ResourceGroup.class);

ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("cuda");
when(image.getImageVersion()).thenReturn("11.8");

Group group1 = mock(Group.class);
when(group1.getUbuntuGid()).thenReturn(10004L);
when(group1.getGroupName()).thenReturn("hyrn");

Group group2 = mock(Group.class);
when(group2.getUbuntuGid()).thenReturn(2001L);
when(group2.getGroupName()).thenReturn("ailab");

RequestGroup rg1 = mock(RequestGroup.class);
when(rg1.getGroup()).thenReturn(group1);

RequestGroup rg2 = mock(RequestGroup.class);
when(rg2.getGroup()).thenReturn(group2);

Request request = mock(Request.class);
when(request.getRequestId()).thenReturn(1L);
when(request.getUbuntuUsername()).thenReturn(username);
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>(Set.of(rg1, rg2)));
when(request.getVolumeSizeGiB()).thenReturn(20L);
when(request.getResourceGroup()).thenReturn(resourceGroup);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(portRequestRepository.findByRequestRequestId(1L)).thenReturn(List.of());
when(nodeRepository.findAllByResourceGroup(resourceGroup)).thenReturn(List.of());

// When
AcceptInfoResponseDTO result = service.getAcceptInfo(username);

// Then
assertThat(result.groups()).hasSize(2);
assertThat(result.groups()).extracting(AcceptInfoResponseDTO.GroupDTO::gid)
.containsExactlyInAnyOrder(10004L, 2001L);
assertThat(result.groups()).extracting(AcceptInfoResponseDTO.GroupDTO::name)
.containsExactlyInAnyOrder("hyrn", "ailab");
}

@Test
@DisplayName("gpu_nodes는 노드명, GPU 수, k8s 포맷 CPU/메모리 제한을 반환한다")
void getAcceptInfo_returnsGpuNodesWithK8sFormat() {
// Given
String username = "testuser";
ResourceGroup resourceGroup = mock(ResourceGroup.class);

Node node = mock(Node.class);
when(node.getNodeId()).thenReturn("farm2");
when(node.getNumberGpu()).thenReturn(2);
when(node.getCpuCoreCount()).thenReturn(4);
when(node.getMemorySizeGB()).thenReturn(8);

ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("cuda");
when(image.getImageVersion()).thenReturn("11.8");

Request request = mock(Request.class);
when(request.getRequestId()).thenReturn(2L);
when(request.getUbuntuUsername()).thenReturn(username);
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>());
when(request.getVolumeSizeGiB()).thenReturn(20L);
when(request.getResourceGroup()).thenReturn(resourceGroup);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(portRequestRepository.findByRequestRequestId(2L)).thenReturn(List.of());
when(nodeRepository.findAllByResourceGroup(resourceGroup)).thenReturn(List.of(node));

// When
AcceptInfoResponseDTO result = service.getAcceptInfo(username);

// Then
assertThat(result.gpu_nodes()).hasSize(1);
AcceptInfoResponseDTO.GpuNodeDTO gpuNode = result.gpu_nodes().get(0);
assertThat(gpuNode.node_name()).isEqualTo("farm2");
assertThat(gpuNode.num_gpu()).isEqualTo(2);
assertThat(gpuNode.cpu_limit()).isEqualTo("4000m");
assertThat(gpuNode.memory_limit()).isEqualTo("8192Mi");
}

@Test
@DisplayName("getAcceptInfo는 PortRequests(additional ports)를 additional_ports로 반환한다")
void getAcceptInfo_returnsAdditionalPortsFromPortRequests() {
// Given
String username = "testuser";
ResourceGroup resourceGroup = mock(ResourceGroup.class);

ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("containerssh-guest");
Expand All @@ -50,10 +167,11 @@ void getAcceptInfo_returnsAdditionalPortsFromPortRequests() {
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>());
when(request.getVolumeSizeGiB()).thenReturn(20L);
when(request.getResourceGroup()).thenReturn(resourceGroup);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(nodeRepository.findAllByResourceGroup(resourceGroup)).thenReturn(List.of());

// additional ports: 사용자가 신청한 포트 (tensorboard, 포트 번호는 시스템 할당)
PortRequests tensorboard = mock(PortRequests.class);
when(tensorboard.getInternalPort()).thenReturn(6006);
when(tensorboard.getUsagePurpose()).thenReturn("tensorboard");
Expand Down Expand Up @@ -86,6 +204,7 @@ void getAcceptInfo_returnsAdditionalPortsFromPortRequests() {
void getAcceptInfo_additionalPortsDoNotContainExternalPort() {
// Given
String username = "testuser2";
ResourceGroup resourceGroup = mock(ResourceGroup.class);

ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("cuda");
Expand All @@ -97,47 +216,35 @@ void getAcceptInfo_additionalPortsDoNotContainExternalPort() {
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>());
when(request.getVolumeSizeGiB()).thenReturn(10L);
when(request.getResourceGroup()).thenReturn(resourceGroup);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(nodeRepository.findAllByResourceGroup(resourceGroup)).thenReturn(List.of());

PortRequests portReq = mock(PortRequests.class);
when(portReq.getInternalPort()).thenReturn(6006);
when(portReq.getUsagePurpose()).thenReturn("tensorboard");
// portNumber(외부포트)는 PortRequests에 있지만 AdditionalPortDTO에는 포함되지 않음

when(portRequestRepository.findByRequestRequestId(2L))
.thenReturn(List.of(portReq));

// When
AcceptInfoResponseDTO result = service.getAcceptInfo(username);

// Then - AdditionalPortDTO는 internal_port, usage_purpose만 갖는다
// Then
assertThat(result.additional_ports()).hasSize(1);
AcceptInfoResponseDTO.AdditionalPortDTO portDTO = result.additional_ports().get(0);
assertThat(portDTO.internal_port()).isEqualTo(6006);
assertThat(portDTO.usage_purpose()).isEqualTo("tensorboard");
// AdditionalPortDTO 구조상 external_port 필드 자체가 존재하지 않음을 타입으로 보장
}

@Test
@DisplayName("additional_ports가 없으면 빈 리스트를 반환한다")
void getAcceptInfo_noAdditionalPorts_returnsEmptyList() {
// Given
String username = "testuser3";

ContainerImage image = mock(ContainerImage.class);
when(image.getImageName()).thenReturn("cuda");
when(image.getImageVersion()).thenReturn("11.8");

Request request = mock(Request.class);
when(request.getRequestId()).thenReturn(3L);
when(request.getUbuntuUsername()).thenReturn(username);
when(request.getContainerImage()).thenReturn(image);
when(request.getRequestGroups()).thenReturn(new LinkedHashSet<>());
when(request.getVolumeSizeGiB()).thenReturn(10L);

when(requestRepository.findByUbuntuUsername(username)).thenReturn(Optional.of(request));
when(portRequestRepository.findByRequestRequestId(3L)).thenReturn(List.of());
ResourceGroup resourceGroup = mock(ResourceGroup.class);
mockRequest(3L, username, resourceGroup);

// When
AcceptInfoResponseDTO result = service.getAcceptInfo(username);
Expand Down