diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java b/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java index b761ba45..7f27f54e 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java @@ -1,6 +1,8 @@ package com.deepdirect.deepwebide_be.repository.controller; import com.deepdirect.deepwebide_be.global.dto.ApiResponseDto; +import com.deepdirect.deepwebide_be.global.exception.ErrorCode; +import com.deepdirect.deepwebide_be.global.exception.GlobalException; import com.deepdirect.deepwebide_be.global.security.CustomUserDetails; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryExecuteResponse; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStatusResponse; @@ -15,6 +17,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.util.Map; diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/domain/PortRegistry.java b/src/main/java/com/deepdirect/deepwebide_be/repository/domain/PortRegistry.java index 64815840..807b07e3 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/domain/PortRegistry.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/domain/PortRegistry.java @@ -1,9 +1,7 @@ package com.deepdirect.deepwebide_be.repository.domain; import jakarta.persistence.*; -import lombok.Getter; -@Getter @Entity @Table(name = "port_registry") public class PortRegistry { @@ -19,7 +17,13 @@ public class PortRegistry { @OneToOne private Repository repository; - public PortRegistry() {} // JPA 기본 생성자 +// public void setStatus(PortStatus status) { +// this.status = status; +// } +// +// public void setRepository(Repository repository) { +// this.repository = repository; +// } public void assignToRepository(Repository repository) { this.status = PortStatus.IN_USE; @@ -31,4 +35,8 @@ public void release() { this.repository = null; } + public Integer getPort() { + return this.port; + } } + diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java index 726373fb..f2b8c769 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java @@ -5,7 +5,6 @@ import com.deepdirect.deepwebide_be.repository.domain.Repository; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; import java.util.Optional; public interface PortRegistryRepository extends JpaRepository { @@ -14,8 +13,4 @@ public interface PortRegistryRepository extends JpaRepository findByRepository(Repository repository); Optional findFirstByStatus(PortStatus status); - - Optional findByPort(Integer port); - List findAllByStatus(PortStatus status); - } diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/RunningContainerRepository.java b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/RunningContainerRepository.java index ee0d3748..3705099a 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/RunningContainerRepository.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/RunningContainerRepository.java @@ -29,7 +29,4 @@ public interface RunningContainerRepository extends JpaRepository findAllByStatusAndCreatedAtBefore(String status, LocalDateTime dateTime); - } diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java index a70e4d22..268811fe 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java @@ -20,18 +20,18 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.LocalDateTime; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -53,26 +53,33 @@ public class RepositoryRunService { @Value("${sandbox.api.base-url}") private String sandboxBaseUrl; - // === 컨테이너 실행 === @Transactional public RepositoryExecuteResponse executeRepository(Long repositoryId, Long userId) { log.info("Starting repository execution - repositoryId: {}, userId: {}", repositoryId, userId); File zipFile = null; try { + // 1. 기존 실행 중인 컨테이너 중지 요청 stopExistingContainer(repositoryId); + // 2. 권한 체크 & 레포지토리 조회 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); + // 3. framework, port 등 정보 추출 String framework = convertTypeToFramework(repo.getRepositoryType()); - Integer port = allocateOrGetPort(repo); // 랜덤 할당 + Integer port = allocateOrGetPort(repo); + // 4. 새로운 UUID 생성 String uuid = UUID.randomUUID().toString(); + // 5. 파일트리를 zip으로 변환 zipFile = fileTreeToZip(repositoryId, uuid); + + // 6. S3 업로드 String s3Url = uploadToS3(zipFile, uuid); + // 7. 샌드박스 실행 요청 SandboxExecutionRequest request = SandboxExecutionRequest.builder() .uuid(uuid) .url(s3Url) @@ -82,11 +89,9 @@ public RepositoryExecuteResponse executeRepository(Long repositoryId, Long userI SandboxExecutionResponse result = sandboxService.requestExecution(request); + // 8. 실행 중인 컨테이너 정보 저장 saveRunningContainer(repositoryId, uuid, "sandbox-" + uuid, port, framework, s3Url); - // **컨테이너 10분 후 자동 만료 비동기 스케줄** - scheduleAutoStopAndRelease(uuid, port); - log.info("Repository execution completed - repositoryId: {}, uuid: {}, port: {}", repositoryId, uuid, port); return RepositoryExecuteResponse.builder() @@ -112,46 +117,6 @@ public RepositoryExecuteResponse executeRepository(Long repositoryId, Long userI } } - // === 컨테이너 만료 비동기 스케줄 === - @Async - public void scheduleAutoStopAndRelease(String uuid, Integer port) { - try { - Thread.sleep(600_000); // 10분 대기 - - boolean stopped = sandboxService.stopContainer(uuid); - if (stopped) { - RunningContainer container = runningContainerRepository.findByUuid(uuid).orElse(null); - if (container != null) { - container.stop(); - runningContainerRepository.save(container); - - PortRegistry portReg = portRegistryRepository.findByPort(port).orElse(null); - if (portReg != null) { - portReg.release(); - portRegistryRepository.save(portReg); - } - } - log.info("컨테이너 {}가 만료되어 중지 및 포트 {} 반납 완료", uuid, port); - } - } catch (Exception e) { - log.error("컨테이너 만료 자동 중지 실패 - uuid: {}", uuid, e); - } - } - - // === 만료 컨테이너 1분마다 백업성 스케줄 === - @Scheduled(fixedDelay = 60_000) - public void autoCleanupExpiredContainers() { - LocalDateTime now = LocalDateTime.now(); - List expired = runningContainerRepository - .findAllByStatusAndCreatedAtBefore("RUNNING", now.minusMinutes(10)); - for (RunningContainer container : expired) { - log.info("자동정리: 10분 초과 컨테이너 발견 {}", container.getUuid()); - stopRepository(container.getRepositoryId(), null); - } - } - - - /** * 기존 실행 중인 컨테이너 중지 */ @@ -193,7 +158,8 @@ public void saveRunningContainer(Long repositoryId, String uuid, String containe Integer port, String framework, String s3Url) { try { RunningContainer container = RunningContainer.builder() - .repositoryId(repositoryId).uuid(uuid) + .repositoryId(repositoryId) + .uuid(uuid) .containerName(containerName) .port(port) .status("RUNNING") @@ -217,16 +183,9 @@ public void saveRunningContainer(Long repositoryId, String uuid, String containe @Transactional public boolean stopRepository(Long repositoryId, Long userId) { try { - // userId가 null이 아니면 권한 체크, null이면 skip - if (userId != null) { - repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) - .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); - } else { - // 그냥 존재하는지 체크 (없으면 그냥 진행) - if (!repositoryRepository.findById(repositoryId).isPresent()) { - log.warn("stopRepository: repositoryId={} not found, but will cleanup container/port anyway.", repositoryId); - } - } + // 권한 체크 + repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) + .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); Optional containerOpt = runningContainerRepository.findByRepositoryId(repositoryId); @@ -243,18 +202,10 @@ public boolean stopRepository(Long repositoryId, Long userId) { if (success) { container.stop(); runningContainerRepository.save(container); - - // 포트 반납도 여기서! - PortRegistry portReg = portRegistryRepository.findByPort(container.getPort()).orElse(null); - if (portReg != null) { - portReg.release(); - portRegistryRepository.save(portReg); - } - log.info("Successfully stopped repository: {} (uuid: {})", repositoryId, container.getUuid()); } - return true; + return success; } catch (Exception e) { log.error("Failed to stop repository: {}", repositoryId, e); @@ -313,7 +264,6 @@ private String convertTypeToFramework(RepositoryType type) { }; } - // === 랜덤 포트 할당 방식으로 수정 === private Integer allocateOrGetPort(Repository repo) { return portRegistryRepository.findByRepository(repo) .map(PortRegistry::getPort) @@ -323,18 +273,16 @@ private Integer allocateOrGetPort(Repository repo) { @Transactional public PortRegistry allocateNewPortForRepository(Repository repo) { log.debug("Allocating new port for repository: {}", repo.getId()); - List availablePorts = portRegistryRepository.findAllByStatus(PortStatus.AVAILABLE); - if (availablePorts.isEmpty()) { - throw new GlobalException(ErrorCode.NO_AVAILABLE_PORT); - } + // 1. 사용 가능한 포트 목록 조회 (status == AVAILABLE) + PortRegistry available = portRegistryRepository.findFirstByStatus(PortStatus.AVAILABLE) + .orElseThrow(() -> new GlobalException(ErrorCode.NO_AVAILABLE_PORT)); - Collections.shuffle(availablePorts); - PortRegistry selected = availablePorts.get(0); - selected.assignToRepository(repo); - PortRegistry savedRegistry = portRegistryRepository.save(selected); + // 2. 해당 포트 할당/저장 + available.assignToRepository(repo); + PortRegistry savedRegistry = portRegistryRepository.save(available); - log.info("Port {} allocated to repository {}", selected.getPort(), repo.getId()); + log.info("Port {} allocated to repository {}", available.getPort(), repo.getId()); return savedRegistry; }