diff --git a/src/main/java/kr/re/etri/autoflow/controllers/ExperimentsController.java b/src/main/java/kr/re/etri/autoflow/controllers/ExperimentsController.java index bd23661..a407451 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/ExperimentsController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/ExperimentsController.java @@ -7,21 +7,39 @@ import kr.re.etri.autoflow.entity.ExperimentsEntity; import kr.re.etri.autoflow.payload.request.ProjectBaseSearchRequest; import kr.re.etri.autoflow.service.ExperimentsService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springdoc.core.annotations.ParameterObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Tag(name = "Experiments", description = "Kubeflow 및 MLflow Experiment API") @RestController @RequestMapping("/api/experiments") @RequiredArgsConstructor +@Slf4j public class ExperimentsController { private final ExperimentsService experimentsService; + private final WebClient.Builder webClientBuilder; + + @Value("${kubeflow.url}") + private String kubeflowBaseUrl; // 예: http://192.168.10.135:32473/ + + @Operation(summary = "모든 Experiments 조회") @GetMapping public ResponseEntity> getAllExperiments() { @@ -48,10 +66,43 @@ public class ExperimentsController { @Operation(summary = "Experiment 등록") @PostMapping - public ResponseEntity createExperiment(@RequestBody ExperimentsEntity experiment) { - return ResponseEntity.ok(experimentsService.save(experiment)); + public Mono> createExperiment(@RequestBody ExperimentsEntity experiment) { + + // 1️⃣ DB 저장 + ExperimentsEntity saved = experimentsService.save(experiment); + + // 2️⃣ Kubeflow POST 요청 payload + Map payload = new HashMap<>(); + payload.put("display_name", saved.getDisplayName()); + payload.put("description", saved.getDescription()); + payload.put("namespace", "default"); // 필요에 따라 변경 + + // 3️⃣ WebClient POST + return webClientBuilder.build() + .post() + .uri(kubeflowBaseUrl + "/apis/v2beta1/experiments") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(payload) + .retrieve() + .bodyToMono(Map.class) // Kubeflow 응답 + .map(resp -> { + // resp에서 필요한 값 추출 후 entity에 반영 + if (resp.get("last_run_created_at") != null) { + String lastRunStr = resp.get("last_run_created_at").toString(); + OffsetDateTime odt = OffsetDateTime.parse(lastRunStr); + saved.setLastRunCreatedAt(odt.withOffsetSameInstant(ZoneId.of("Asia/Seoul").getRules().getOffset(odt.toInstant())) + .toLocalDateTime()); + } + if(resp.get("id") != null) { + saved.setKubeFlowId(resp.get("id").toString()); + } + return saved; + }) + .map(entity -> ResponseEntity.ok(entity)) + .doOnError(e -> log.error("Kubeflow experiment 등록 실패", e)); } + @Operation(summary = "Experiment 수정") @PutMapping("/{id}") public ResponseEntity updateExperiment( diff --git a/src/main/java/kr/re/etri/autoflow/controllers/MinioAttachmentController.java b/src/main/java/kr/re/etri/autoflow/controllers/MinioAttachmentController.java index 3b8f753..96f3956 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/MinioAttachmentController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/MinioAttachmentController.java @@ -112,32 +112,32 @@ public class MinioAttachmentController { } } - @GetMapping("/download_new") - public ResponseEntity downloadFile_new(@RequestParam String objectName) { - try { - // MinIO에서 스트리밍으로 가져오기 - InputStream is = minioClient.getObject( - GetObjectArgs.builder() - .bucket("mlpipeline") - .object(objectName) - .build() - ); - - InputStreamResource resource = new InputStreamResource(is); - - String encodedFileName = URLEncoder.encode(objectName, StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName) - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(resource); - - } catch (Exception e) { - log.error("파일 다운로드 실패", e); - return ResponseEntity.internalServerError().build(); - } - } +// @GetMapping("/download_new") +// public ResponseEntity downloadFile_new(@RequestParam String objectName) { +// try { +// // MinIO에서 스트리밍으로 가져오기 +// InputStream is = minioClient.getObject( +// GetObjectArgs.builder() +// .bucket("mlpipeline") +// .object(objectName) +// .build() +// ); +// +// InputStreamResource resource = new InputStreamResource(is); +// +// String encodedFileName = URLEncoder.encode(objectName, StandardCharsets.UTF_8) +// .replaceAll("\\+", "%20"); +// +// return ResponseEntity.ok() +// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName) +// .contentType(MediaType.APPLICATION_OCTET_STREAM) +// .body(resource); +// +// } catch (Exception e) { +// log.error("파일 다운로드 실패", e); +// return ResponseEntity.internalServerError().build(); +// } +// } @Operation(summary = "MinIO YAML 파일 읽기", description = "MinIO에서 YAML 파일을 다운로드하여 텍스트로 반환합니다.") diff --git a/src/main/java/kr/re/etri/autoflow/entity/ExperimentsEntity.java b/src/main/java/kr/re/etri/autoflow/entity/ExperimentsEntity.java index 2af84b9..7461917 100644 --- a/src/main/java/kr/re/etri/autoflow/entity/ExperimentsEntity.java +++ b/src/main/java/kr/re/etri/autoflow/entity/ExperimentsEntity.java @@ -22,7 +22,7 @@ public class ExperimentsEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Schema(description = "DB PK") + @Schema(description = "DB PK", example = "null") private Long id; @Schema(description = "Kubeflow / MLflow Experiment ID") @@ -41,13 +41,13 @@ public class ExperimentsEntity { private String description; @Schema(description = "MLflow Artifact 경로 / Kubeflow에는 해당 없음") - private String artifactLocation; + private String mlflow_artifactLocation; @Schema(description = "MLflow 상태 / lifecycle_stage, Kubeflow에는 해당 없음") - private String lifecycleStage; + private String mlflowLifecycleStage; @Schema(description = "Kubeflow 저장 상태 / storage_state, MLflow에는 해당 없음") - private String storageState; + private String kubeflowStorageState; @CreatedDate @Schema(description = "Experiment 생성 일시 / Kubeflow: ISO 8601 created_at, MLflow: timestamp(ms) → LocalDateTime") @@ -63,7 +63,7 @@ public class ExperimentsEntity { private LocalDateTime lastUpdateTime; @Schema(description = "Kubeflow 마지막 Run 생성 일시 / last_run_created_at") - private LocalDateTime lastRunCreatedAt; + private LocalDateTime kubeflowLastRunCreatedAt; @Schema(description = "등록자 ID") private String regUserId;