[ADD] CreateRunRequest DTO 및 Kubeflow Run 생성 API 구현 (Controller, Service)

main
bjkim 9 months ago
parent baca2e29a7
commit eb2e430669

@ -1,7 +1,11 @@
package kr.re.etri.autoflow.controllers;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import kr.re.etri.autoflow.entity.WorkflowEntity;
import kr.re.etri.autoflow.payload.request.CreateRunRequest;
import kr.re.etri.autoflow.service.PipelineUploadService;
import kr.re.etri.autoflow.service.WorkFlowService;
import lombok.RequiredArgsConstructor;
@ -10,58 +14,40 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/pipelines")
@RequiredArgsConstructor
@CrossOrigin(origins = "*") // 모든 도메인 허용
@io.swagger.v3.oas.annotations.tags.Tag(name = "Kubeflow Pipeline", description = "Kubeflow 파이프라인 업로드 API")
@CrossOrigin(origins = "*")
@io.swagger.v3.oas.annotations.tags.Tag(name = "Kubeflow Pipeline", description = "Kubeflow 파이프라인 API")
public class PipelineUploadController {
private final PipelineUploadService pipelineUploadService;
private final WorkFlowService workFlowService;
@io.swagger.v3.oas.annotations.Operation(
summary = "파이프라인 업로드",
description = "Kubeflow에 파이프라인 파일(Multipart)을 업로드하고, 업로드 결과를 반환합니다."
)
@io.swagger.v3.oas.annotations.responses.ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "파이프라인 업로드 성공"
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "400",
description = "잘못된 요청"
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "500",
description = "서버 내부 오류"
)
@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<Map<String, Object>> uploadPipeline(
@RequestParam("uploadfile") MultipartFile file,
@RequestParam(value = "name", required = true) String name,
@RequestParam(value = "display_name", required = true) String displayName,
@RequestParam("name") String name,
@RequestParam("display_name") String displayName,
@RequestParam(value = "description", required = false) String description,
@RequestParam(value = "namespace", required = false) String namespace,
@RequestParam(value = "regUserId") String regUserId,
@RequestParam(value = "projectId") Long projectId
@RequestParam("regUserId") String regUserId,
@RequestParam("projectId") Long projectId
) {
try {
// Kubeflow 업로드
Map<String, Object> result = pipelineUploadService.uploadPipeline(file, name, displayName, description, namespace);
// WorkflowEntity 매핑 후 DB 저장
WorkflowEntity workflow = WorkflowEntity.builder()
.pipelineId((String) result.get("pipeline_id"))
.displayName((String) result.get("display_name"))
@ -74,19 +60,50 @@ public class PipelineUploadController {
.version(1)
.build();
// 저장
workFlowService.save(workflow);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("Pipeline upload failed", e);
Map<String, Object> error = Map.of(
"status", 500,
"error", "Internal Server Error",
"message", e.getMessage()
);
return ResponseEntity.status(500).body(error);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of(
"status", 500,
"error", "Internal Server Error",
"message", e.getMessage()
));
}
}
@PostMapping("/runs")
@Operation(summary = "Kubeflow Run 생성", description = "Kubeflow에 Run을 생성합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Run 생성 성공"),
@ApiResponse(responseCode = "400", description = "display_name 필수"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
public ResponseEntity<Map<String, Object>> createRun(
@Parameter @RequestBody CreateRunRequest runRequest
) {
try {
Map<String, Object> result = pipelineUploadService.createRun(runRequest);
return ResponseEntity.ok(result);
} catch (IllegalArgumentException e) {
log.error("Invalid run request", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of(
"status", 400,
"error", "Bad Request",
"message", e.getMessage()
));
} catch (Exception e) {
log.error("Run creation failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of(
"status", 500,
"error", "Internal Server Error",
"message", e.getMessage()
));
}
}
}

@ -0,0 +1,43 @@
package kr.re.etri.autoflow.payload.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
@Data
@Schema(description = "Kubeflow Run 생성 요청 DTO")
public class CreateRunRequest {
@Schema(description = "Run 이름 (필수)", required = true, example = "Run of 435345 (5c7b9)")
private String display_name;
@Schema(description = "Run 설명", example = "테스트 Run")
private String description;
@Schema(description = "파이프라인 버전 참조", required = true)
private PipelineVersionReference pipeline_version_reference;
@Schema(description = "런 타임 구성")
private RuntimeConfig runtime_config;
@Schema(description = "서비스 계정", example = "pipeline-runner")
private String service_account;
@Data
@Schema(description = "Pipeline Version Reference")
public static class PipelineVersionReference {
@Schema(description = "파이프라인 ID", required = true)
private String pipeline_id;
@Schema(description = "파이프라인 버전 ID", required = true)
private String pipeline_version_id;
}
@Data
@Schema(description = "Runtime Config")
public static class RuntimeConfig {
@Schema(description = "파라미터", example = "{}")
private Map<String, Object> parameters;
}
}

@ -1,6 +1,9 @@
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;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -18,20 +21,23 @@ import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class PipelineUploadService {
private final RestTemplate restTemplate;
@Value("${kubeflow.pipeline.upload-url}")
private String kubeflowUploadUrl;
@Value("${kubeflow.url}")
private String kubeflowBaseUrl; // 예: http://192.168.10.135:32473/
public Map<String, Object> uploadPipeline(MultipartFile file,
String name,
String displayName,
String description,
String namespace) {
/**
* Pipeline
*/
public Map uploadPipeline(MultipartFile file,
String name,
String displayName,
String description,
String namespace) {
try {
// 파일 form-data
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("uploadfile", new MultipartInputStreamFileResource(file.getInputStream(), file.getOriginalFilename()));
@ -40,21 +46,47 @@ public class PipelineUploadService {
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
// URL 조립 (쿼리 파라미터 방식)
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(kubeflowUploadUrl);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(kubeflowBaseUrl + "apis/v2beta1/pipelines/upload");
if (name != null && !name.isBlank()) builder.queryParam("name", name);
if (displayName != null && !displayName.isBlank()) builder.queryParam("display_name", displayName);
if (description != null && !description.isBlank()) builder.queryParam("description", description);
if (namespace != null && !namespace.isBlank()) builder.queryParam("namespace", namespace);
String url = builder.toUriString();
ResponseEntity<Map> response = restTemplate.postForEntity(url, requestEntity, Map.class);
ResponseEntity<Map> response = restTemplate.postForEntity(builder.toUriString(), requestEntity, Map.class);
return response.getBody();
} catch (IOException e) {
throw new RuntimeException("Pipeline upload failed", e);
}
}
/**
* Run
* runRequest display_name, pipeline_version_reference, runtime_config
*/
// PipelineUploadService.java
public Map<String, Object> createRun(CreateRunRequest runRequest) {
try {
// URL 조립
String url = kubeflowBaseUrl + "apis/v2beta1/runs";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// DTO를 Map으로 변환해서 전송
ObjectMapper objectMapper = new ObjectMapper();
Map body = objectMapper.convertValue(runRequest, Map.class);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(url, requestEntity, Map.class);
return response.getBody();
} catch (Exception e) {
if (e instanceof IllegalArgumentException) throw e;
throw new RuntimeException("Kubeflow Run creation failed", e);
}
}
}

Loading…
Cancel
Save