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,8 +1,6 @@
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;
Expand All @@ -17,7 +15,6 @@
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;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.deepdirect.deepwebide_be.repository.domain;

import jakarta.persistence.*;
import lombok.Getter;

@Getter
@Entity
@Table(name = "port_registry")
public class PortRegistry {
Expand All @@ -17,13 +19,7 @@ public class PortRegistry {
@OneToOne
private Repository repository;

// public void setStatus(PortStatus status) {
// this.status = status;
// }
//
// public void setRepository(Repository repository) {
// this.repository = repository;
// }
public PortRegistry() {} // JPA ๊ธฐ๋ณธ ์ƒ์„ฑ์ž

public void assignToRepository(Repository repository) {
this.status = PortStatus.IN_USE;
Expand All @@ -35,8 +31,4 @@ public void release() {
this.repository = null;
}

public Integer getPort() {
return this.port;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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<PortRegistry, Long> {
Expand All @@ -13,4 +14,8 @@ public interface PortRegistryRepository extends JpaRepository<PortRegistry, Long

Optional<PortRegistry> findByRepository(Repository repository);
Optional<PortRegistry> findFirstByStatus(PortStatus status);

Optional<PortRegistry> findByPort(Integer port);
List<PortRegistry> findAllByStatus(PortStatus status);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ public interface RunningContainerRepository extends JpaRepository<RunningContain

@Query("SELECT COUNT(rc) FROM RunningContainer rc WHERE rc.status = 'RUNNING'")
long countRunningContainers();

List<RunningContainer> findAllByStatusAndCreatedAtBefore(String status, LocalDateTime dateTime);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.http.HttpMethod;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
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;
Expand All @@ -53,33 +53,26 @@ 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)
Expand All @@ -89,9 +82,11 @@ 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()
Expand All @@ -117,6 +112,46 @@ 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<RunningContainer> expired = runningContainerRepository
.findAllByStatusAndCreatedAtBefore("RUNNING", now.minusMinutes(10));
for (RunningContainer container : expired) {
log.info("์ž๋™์ •๋ฆฌ: 10๋ถ„ ์ดˆ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๋ฐœ๊ฒฌ {}", container.getUuid());
stopRepository(container.getRepositoryId(), null);
}
}



/**
* ๊ธฐ์กด ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ ์ค‘์ง€
*/
Expand Down Expand Up @@ -158,8 +193,7 @@ 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")
Expand All @@ -183,9 +217,16 @@ public void saveRunningContainer(Long repositoryId, String uuid, String containe
@Transactional
public boolean stopRepository(Long repositoryId, Long userId) {
try {
// ๊ถŒํ•œ ์ฒดํฌ
repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId)
.orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND));
// 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);
}
}

Optional<RunningContainer> containerOpt = runningContainerRepository.findByRepositoryId(repositoryId);

Expand All @@ -202,10 +243,18 @@ 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 success;
return true;

} catch (Exception e) {
log.error("Failed to stop repository: {}", repositoryId, e);
Expand Down Expand Up @@ -264,6 +313,7 @@ private String convertTypeToFramework(RepositoryType type) {
};
}

// === ๋žœ๋ค ํฌํŠธ ํ• ๋‹น ๋ฐฉ์‹์œผ๋กœ ์ˆ˜์ • ===
private Integer allocateOrGetPort(Repository repo) {
return portRegistryRepository.findByRepository(repo)
.map(PortRegistry::getPort)
Expand All @@ -273,16 +323,18 @@ private Integer allocateOrGetPort(Repository repo) {
@Transactional
public PortRegistry allocateNewPortForRepository(Repository repo) {
log.debug("Allocating new port for repository: {}", repo.getId());
List<PortRegistry> availablePorts = portRegistryRepository.findAllByStatus(PortStatus.AVAILABLE);

// 1. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํฌํŠธ ๋ชฉ๋ก ์กฐํšŒ (status == AVAILABLE)
PortRegistry available = portRegistryRepository.findFirstByStatus(PortStatus.AVAILABLE)
.orElseThrow(() -> new GlobalException(ErrorCode.NO_AVAILABLE_PORT));
if (availablePorts.isEmpty()) {
throw new GlobalException(ErrorCode.NO_AVAILABLE_PORT);
}

// 2. ํ•ด๋‹น ํฌํŠธ ํ• ๋‹น/์ €์žฅ
available.assignToRepository(repo);
PortRegistry savedRegistry = portRegistryRepository.save(available);
Collections.shuffle(availablePorts);
PortRegistry selected = availablePorts.get(0);
selected.assignToRepository(repo);
PortRegistry savedRegistry = portRegistryRepository.save(selected);

log.info("Port {} allocated to repository {}", available.getPort(), repo.getId());
log.info("Port {} allocated to repository {}", selected.getPort(), repo.getId());
return savedRegistry;
}

Expand Down