diff --git a/src/main/java/kr/re/etri/autoflow/controllers/KubeflowExperimentsController.java b/src/main/java/kr/re/etri/autoflow/controllers/KubeflowExperimentsController.java new file mode 100644 index 0000000..8c0f34b --- /dev/null +++ b/src/main/java/kr/re/etri/autoflow/controllers/KubeflowExperimentsController.java @@ -0,0 +1,104 @@ +package kr.re.etri.autoflow.controllers; + +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.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import kr.re.etri.autoflow.service.PipelineUploadService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +@io.swagger.v3.oas.annotations.tags.Tag(name = "Kubeflow Pipeline", description = "Kubeflow 파이프라인 API") +public class KubeflowExperimentsController { + private final PipelineUploadService pipelineUploadService; + + @Operation( + summary = "실험 목록 조회", + description = "Kubeflow에 등록된 Experiment 리스트를 조회합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "실험 목록 조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content) + }) + @GetMapping("/experiments") + public ResponseEntity> listExperiments( + @Parameter(description = "조회할 namespace (생략 시 기본값)") + @RequestParam(value = "namespace", required = false) String namespace, + @Parameter(description = "페이지 크기 (기본값: 10)") + @RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize, + @Parameter(description = "다음 페이지 토큰") + @RequestParam(value = "pageToken", required = false) String pageToken + ) { + try { + Map result = pipelineUploadService.listExperiments(namespace, pageSize, pageToken); + return ResponseEntity.ok(result); + + } catch (Exception e) { + log.error("List experiments failed", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of( + "status", 500, + "error", "Internal Server Error", + "message", e.getMessage() + )); + } + } + + @Operation( + summary = "실험 단건 조회", + description = "experiment_id로 Kubeflow Experiment 상세 정보를 조회합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "실험 조회 성공"), + @ApiResponse(responseCode = "404", description = "실험을 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 내부 오류") + }) + @GetMapping("/experiments/{experimentId}") + public ResponseEntity getExperimentById( + @Parameter(description = "조회할 Experiment ID") @PathVariable String experimentId + ) { + try { + Map result = pipelineUploadService.getExperimentById(experimentId); + + if (result == null || result.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of( + "status", 404, + "error", "Not Found", + "message", "Experiment not found" + )); + } + + return ResponseEntity.ok(result); + + } catch (IllegalArgumentException e) { + log.warn("잘못된 요청: experimentId={}", experimentId, e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of( + "status", 400, + "error", "Bad Request", + "message", "Invalid experiment ID format" + )); + + } catch (Exception e) { + log.error("Experiment 조회 실패: experimentId={}", experimentId, e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of( + "status", 500, + "error", "Internal Server Error", + "message", "An unexpected error occurred while retrieving experiment" + )); + } + } +} diff --git a/src/main/java/kr/re/etri/autoflow/controllers/PipelineUploadController.java b/src/main/java/kr/re/etri/autoflow/controllers/PipelineUploadController.java index 2286d41..a4165a6 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/PipelineUploadController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/PipelineUploadController.java @@ -3,6 +3,7 @@ package kr.re.etri.autoflow.controllers; import io.minio.MinioClient; 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.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import kr.re.etri.autoflow.entity.MinioAttachmentEntity; @@ -35,53 +36,6 @@ public class PipelineUploadController { private final MinioAttachmentService minioAttachmentService; -// @Operation(summary = "파이프라인 업로드", description = "Kubeflow에 파이프라인 파일(Multipart)을 업로드") -// @ApiResponses({ -// @ApiResponse(responseCode = "200", description = "파이프라인 업로드 성공"), -// @ApiResponse(responseCode = "400", description = "잘못된 요청"), -// @ApiResponse(responseCode = "500", description = "서버 내부 오류") -// }) -// @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) -// public ResponseEntity> uploadPipe( -// @RequestParam("uploadfile") MultipartFile file, -// @RequestParam("name") String name, -// @RequestParam("display_name") String displayName, -// @RequestParam(value = "description", required = false) String description, -// @RequestParam(value = "namespace", required = false) String namespace, -// @RequestParam("regUserId") String regUserId, -// @RequestParam("projectId") Long projectId -// ) { -// try { -// Map result = pipelineUploadService.uploadPipeline(file, name, displayName, description, namespace); -// -// WorkflowEntity workflow = WorkflowEntity.builder() -// .pipelineId((String) result.get("pipeline_id")) -// .displayName((String) result.get("display_name")) -// .name((String) result.get("name")) -// .description((String) result.get("description")) -// .namespace((String) result.get("namespace")) -// .regUserId(regUserId) -// .projectId(projectId) -// .kubeflowStatus("Created") -// .version(1) -// .build(); -// -// workFlowService.save(workflow); -// -// return ResponseEntity.ok(result); -// -// } catch (Exception e) { -// log.error("Pipeline upload failed", e); -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) -// .body(Map.of( -// "status", 500, -// "error", "Internal Server Error", -// "message", e.getMessage() -// )); -// } -// } - - @Operation(summary = "파이프라인 생성 및 MinIO업로드", description = "Kubeflow에 파이프라인 파일(Multipart)을 업로드하고 MinIO에 저장") @ApiResponses({ @ApiResponse(responseCode = "200", description = "파이프라인 업로드 및 MinIO 저장 성공"), diff --git a/src/main/java/kr/re/etri/autoflow/service/PipelineUploadService.java b/src/main/java/kr/re/etri/autoflow/service/PipelineUploadService.java index a7d54a3..9ec2341 100644 --- a/src/main/java/kr/re/etri/autoflow/service/PipelineUploadService.java +++ b/src/main/java/kr/re/etri/autoflow/service/PipelineUploadService.java @@ -1,6 +1,5 @@ package kr.re.etri.autoflow.service; -import com.fasterxml.jackson.databind.ObjectMapper; import kr.re.etri.autoflow.payload.request.CreateRunRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -90,4 +89,52 @@ public class PipelineUploadService { } } + /** + * Experiments 조회 + */ + public Map listExperiments(String namespace, int pageSize, String pageToken) { + try { + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(kubeflowBaseUrl + "/apis/v2beta1/experiments"); + + if (namespace != null && !namespace.isBlank()) { + builder.queryParam("namespace", namespace); + } + if (pageSize > 0) { + builder.queryParam("page_size", pageSize); + } + if (pageToken != null && !pageToken.isBlank()) { + builder.queryParam("page_token", pageToken); + } + + return webClient.get() + .uri(builder.toUriString()) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(Map.class) + .block(); + + } catch (Exception e) { + throw new RuntimeException("Kubeflow Experiments 조회 실패", e); + } + } + + /** + * Experiment 단건 조회 + */ + public Map getExperimentById(String experimentId) { + try { + String url = kubeflowBaseUrl + "/apis/v2beta1/experiments/" + experimentId; + + return webClient.get() + .uri(url) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(Map.class) + .block(); + + } catch (Exception e) { + throw new RuntimeException("Kubeflow experiment 조회 실패: " + experimentId, e); + } + } }