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
Expand Up @@ -2,10 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@Async
public class DeepwebideBeApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.deepdirect.deepwebide_be.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
@EnableScheduling
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("auto-stop-scheduler-");
return scheduler;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
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;
import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStopResponse;
import com.deepdirect.deepwebide_be.repository.repository.RepositoryRepository;
import com.deepdirect.deepwebide_be.repository.service.AutoStopSchedulerService;
import com.deepdirect.deepwebide_be.repository.service.RepositoryRunService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -30,7 +28,7 @@
public class RepositoryRunController {

private final RepositoryRunService repositoryRunService;
private final RepositoryRepository repositoryRepository;
private final AutoStopSchedulerService autoStopSchedulerService;

@PostMapping("/{repositoryId}/execute")
@Operation(summary = "레포지토리 실행", description = "레포지토리를 실행하고 실행 결과를 반환합니다.")
Expand All @@ -39,6 +37,7 @@ public ResponseEntity<ApiResponseDto<RepositoryExecuteResponse>> executeReposito
@PathVariable Long repositoryId
) {
RepositoryExecuteResponse resp = repositoryRunService.executeRepository(repositoryId, userDetails.getId());
autoStopSchedulerService.scheduleAutoStop(repositoryId, resp.getUuid(), 10);
return ResponseEntity.ok(ApiResponseDto.of(200, "레포지토리 실행 요청 완료", resp));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

public interface PortRegistryRepository extends JpaRepository<PortRegistry, Long> {

Optional<PortRegistry> findFirstByStatusOrderByPortAsc(PortStatus status);

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.deepdirect.deepwebide_be.repository.service;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;

import java.util.Date;


@Service
@RequiredArgsConstructor
public class AutoStopSchedulerService {
private final RepositoryRunService repositoryRunService;
private final TaskScheduler taskScheduler;

// 10분 후 자동 중지 예약
public void scheduleAutoStop(Long repositoryId, String uuid, int minutes) {
taskScheduler.schedule(
() -> repositoryRunService.stopIfTimeout(repositoryId, uuid),
new Date(System.currentTimeMillis() + minutes * 60 * 1000L)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
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.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.*;
Expand Down Expand Up @@ -196,6 +194,14 @@ public boolean stopRepository(Long repositoryId, Long userId) {

RunningContainer container = containerOpt.get();

// 포트 정보 해제 로직 추가
Optional<PortRegistry> portRegistryOpt = portRegistryRepository.findByRepositoryId(repositoryId);
portRegistryOpt.ifPresent(portRegistry -> {
portRegistry.release();
portRegistryRepository.save(portRegistry);
log.info("Released port {} for repository {}", portRegistry.getPort(), repositoryId);
});

// 샌드박스 서버에 중지 요청
boolean success = sandboxService.stopContainer(container.getUuid());

Expand Down Expand Up @@ -446,9 +452,11 @@ public Map<String, Object> getRepositoryLogs(Long repositoryId, Long userId, int

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

Integer portValue = containerOpt.map(RunningContainer::getPort).orElse(-1);

if (containerOpt.isEmpty()) {
return Map.of(
"port", null,
"port", portValue, // -1로 반환
"logs", "실행 중인 컨테이너가 없습니다."
);
}
Expand All @@ -473,44 +481,52 @@ public Map<String, Object> getRepositoryLogs(Long repositoryId, Long userId, int
runningContainerRepository.save(container);

return Map.of(
"port", null,
"port", portValue,
"logs", "컨테이너가 존재하지 않아 중지되었습니다."
);
}

// 정상 응답 - 포트와 로그만 반환
String logs = extractLogs(response);
if (logs == null) logs = "";
return Map.of(
"port", container.getPort(),
"port", portValue,
"logs", logs
);
}

return Map.of(
"port", container.getPort(),
"port", portValue,
"logs", "로그를 가져올 수 없습니다."
);

} catch (Exception httpEx) {
log.error("HTTP request failed - url: {}", url, httpEx);

String msg = httpEx.getMessage();
if (msg == null) msg = "알 수 없는 오류";

// HTTP 오류 시에도 컨테이너 상태 확인
if (httpEx.getMessage().contains("404") || httpEx.getMessage().contains("Not Found")) {
if (msg.contains("404") || msg.contains("Not Found")) {
container.stop();
runningContainerRepository.save(container);
}

return Map.of(
"port", container.getPort(),
"logs", "로그 조회 중 오류가 발생했습니다: " + httpEx.getMessage()
"port", portValue,
"logs", "로그 조회 중 오류가 발생했습니다: " + msg
);
}

} catch (Exception e) {
log.error("Failed to get repository logs: {}", repositoryId, e);

String msg = e.getMessage();
if (msg == null) msg = "알 수 없는 오류";

return Map.of(
"port", null,
"logs", "오류가 발생했습니다: " + e.getMessage()
"port", -1,
"logs", "오류가 발생했습니다: " + msg
);
}
}
Expand All @@ -536,6 +552,19 @@ private String extractLogs(Map<String, Object> response) {
return result.isEmpty() ? "로그가 없습니다." : result;
}

@Transactional
public void stopIfTimeout(Long repositoryId, String uuid) {
Optional<RunningContainer> containerOpt = runningContainerRepository.findByRepositoryId(repositoryId);
if (containerOpt.isPresent() && "RUNNING".equals(containerOpt.get().getStatus())) {
log.info("자동 중지 조건 충족: repositoryId={}, uuid={}", repositoryId, uuid);
Repository repo = repositoryRepository.findById(repositoryId)
.orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND));
stopRepository(repositoryId, repo.getOwner().getId());
} else {
log.info("이미 중지된 컨테이너: repositoryId={}, uuid={}", repositoryId, uuid);
}
}

private Map<String, Object> createContainerInfo(RunningContainer container, String actualStatus) {
return Map.of(
"uuid", container.getUuid(),
Expand Down