diff --git a/build.gradle b/build.gradle index 7d74d37..fd968fb 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ repositories { dependencies { implementation "com.google.cloud.functions:functions-framework-api:1.1.0" implementation "com.google.cloud.functions.invoker:java-function-invoker:1.3.1" - implementation 'com.google.cloud:google-cloud-run:0.34.0' + implementation 'com.google.cloud:google-cloud-tasks:2.34.0' implementation 'com.google.cloud:google-cloud-scheduler:2.33.0' implementation "com.fasterxml.jackson.core:jackson-databind:2.16.1" diff --git a/src/main/java/com/telecom/fn/batchcontrol/service/BatchRunService.java b/src/main/java/com/telecom/fn/batchcontrol/service/BatchRunService.java index 2a276f9..55a2faa 100644 --- a/src/main/java/com/telecom/fn/batchcontrol/service/BatchRunService.java +++ b/src/main/java/com/telecom/fn/batchcontrol/service/BatchRunService.java @@ -1,54 +1,17 @@ package com.telecom.fn.batchcontrol.service; -import com.google.cloud.run.v2.JobName; -import com.google.cloud.run.v2.JobsClient; -import com.google.cloud.run.v2.RunJobRequest; import com.telecom.fn.batchcontrol.model.BatchJobType; -import java.util.List; - public class BatchRunService { - private static final String PROJECT_ID = System.getenv("GCP_PROJECT"); - private static final String REGION = System.getenv("GCP_REGION"); - private static final String CLOUD_RUN_JOB_NAME = "telecom-app-batch-core"; - + private final CloudTasksService cloudTasksService = new CloudTasksService(); public void runJob(BatchJobType jobType, String invMonth) throws Exception { - System.out.println("[BatchRunService] ENTER"); System.out.println("[BatchRunService] jobType=" + jobType + ", invMonth=" + invMonth); - System.out.println("[BatchRunService] PROJECT_ID=" + PROJECT_ID + ", REGION=" + REGION); - - List args = List.of( - "--spring.batch.job.name=" + jobType.springBatchJobName(), - "--invMonth=" + invMonth, - "--spring.batch.job.enabled=true", - "--spring.main.web-application-type=none" - ); - - System.out.println("[BatchRunService] args=" + args); - - try (JobsClient client = JobsClient.create()) { - - String jobName = JobName.of(PROJECT_ID, REGION, CLOUD_RUN_JOB_NAME).toString(); - System.out.println("[BatchRunService] CloudRunJob=" + jobName); - - RunJobRequest request = RunJobRequest.newBuilder() - .setName(jobName) - .setOverrides( - RunJobRequest.Overrides.newBuilder() - .addContainerOverrides( - RunJobRequest.Overrides.ContainerOverride.newBuilder() - .addAllArgs(args) - .build() - ) - .build() - ) - .build(); + System.out.println("[BatchRunService] Delegating to CloudTasksService..."); - client.runJobAsync(request); - System.out.println("[BatchRunService] runJobAsync CALLED"); - } + String taskName = cloudTasksService.createTask(jobType, invMonth); + System.out.println("[BatchRunService] Task created: " + taskName); } -} \ No newline at end of file +} diff --git a/src/main/java/com/telecom/fn/batchcontrol/service/BatchScheduleService.java b/src/main/java/com/telecom/fn/batchcontrol/service/BatchScheduleService.java index 2c43c96..5eed6ac 100644 --- a/src/main/java/com/telecom/fn/batchcontrol/service/BatchScheduleService.java +++ b/src/main/java/com/telecom/fn/batchcontrol/service/BatchScheduleService.java @@ -22,7 +22,6 @@ public class BatchScheduleService { private static final String PROJECT_ID = System.getenv("GCP_PROJECT"); private static final String REGION = System.getenv("GCP_REGION"); - private static final String CLOUD_RUN_JOB_NAME = "telecom-app-batch-core"; private static CloudSchedulerClient schedulerClient; @@ -44,7 +43,7 @@ public ScheduleResponse getJobSchedule(BatchJobType jobType) throws Exception { try { Job job = client.getJob(fullJobName); String body = job.getHttpTarget().getBody().toStringUtf8(); - String invMonth = extractInvMonth(body); + String invMonth = CloudTasksService.extractInvMonthFromSchedulerBody(body); return new ScheduleResponse( jobType, @@ -60,16 +59,6 @@ public ScheduleResponse getJobSchedule(BatchJobType jobType) throws Exception { } } - private String extractInvMonth(String body) { - if (body.contains("--invMonth=")) { - int start = body.indexOf("--invMonth=") + 11; - int end = body.indexOf("\"", start); - if (end == -1) end = body.indexOf("\"", start); - if (end != -1) return body.substring(start, end); - } - return null; - } - public java.util.List getAllJobSchedules() throws Exception { java.util.List list = new java.util.ArrayList<>(); for (BatchJobType type : BatchJobType.values()) { @@ -87,25 +76,20 @@ public void createJobSchedule(BatchJobType jobType, String cron, String invMonth LocationName parent = LocationName.of(PROJECT_ID, REGION); String fullJobName = JobName.of(PROJECT_ID, REGION, jobType.schedulerJobName()).toString(); - - String saEmail = getDefaultServiceAccount(); - String uri = String.format( - "https://run.googleapis.com/v2/projects/%s/locations/%s/jobs/%s:run", - PROJECT_ID, REGION, CLOUD_RUN_JOB_NAME - ); - String jobArg = "\"--spring.batch.job.name=" + jobType.springBatchJobName() + "\""; - String monthArg = (invMonth != null && !invMonth.isBlank()) ? ", \"--invMonth=" + invMonth + "\"" : ""; + String saEmail = getDefaultServiceAccount(); + String uri = CloudTasksService.getCloudTasksApiUrl(); - String jsonBody = "{\"overrides\": {\"container_overrides\": [{\"args\": [" + jobArg + monthArg + "]}]}}"; + String jsonBody = CloudTasksService.buildSchedulerTaskRequestBody(jobType, invMonth); - HttpTarget httpTarget = HttpTarget.newBuilder() + HttpTarget httpTarget = HttpTarget.newBuilder() .setUri(uri) .setHttpMethod(HttpMethod.POST) - .setOauthToken(OAuthToken.newBuilder() + .setOauthToken(OAuthToken.newBuilder() .setServiceAccountEmail(saEmail) .setScope("https://www.googleapis.com/auth/cloud-platform") .build()) + .putHeaders("Content-Type", "application/json") .setBody(ByteString.copyFrom(jsonBody, StandardCharsets.UTF_8)) .build(); @@ -140,15 +124,8 @@ public void updateJobSchedule(BatchJobType jobType, String cron, String invMonth jobType.schedulerJobName() ).toString(); - String uri = String.format( - "https://run.googleapis.com/v2/projects/%s/locations/%s/jobs/%s:run", - PROJECT_ID, REGION, CLOUD_RUN_JOB_NAME - ); - - String jobArg = "\"--spring.batch.job.name=" + jobType.springBatchJobName() + "\""; - String monthArg = (invMonth != null && !invMonth.isBlank()) ? ", \"--invMonth=" + invMonth + "\"" : ""; - - String jsonBody = "{\"overrides\": {\"container_overrides\": [{\"args\": [" + jobArg + monthArg + "]}]}}"; + String uri = CloudTasksService.getCloudTasksApiUrl(); + String jsonBody = CloudTasksService.buildSchedulerTaskRequestBody(jobType, invMonth); HttpTarget httpTarget = HttpTarget.newBuilder() .setUri(uri) @@ -157,6 +134,7 @@ public void updateJobSchedule(BatchJobType jobType, String cron, String invMonth .setServiceAccountEmail(getDefaultServiceAccount()) .setScope("https://www.googleapis.com/auth/cloud-platform") .build()) + .putHeaders("Content-Type", "application/json") .setBody(ByteString.copyFrom(jsonBody, StandardCharsets.UTF_8)) .build(); @@ -169,7 +147,9 @@ public void updateJobSchedule(BatchJobType jobType, String cron, String invMonth FieldMask fieldMask = FieldMask.newBuilder() .addPaths("schedule") + .addPaths("http_target.uri") .addPaths("http_target.body") + .addPaths("http_target.headers") .build(); UpdateJobRequest request = UpdateJobRequest.newBuilder() diff --git a/src/main/java/com/telecom/fn/batchcontrol/service/CloudTasksService.java b/src/main/java/com/telecom/fn/batchcontrol/service/CloudTasksService.java new file mode 100644 index 0000000..35831eb --- /dev/null +++ b/src/main/java/com/telecom/fn/batchcontrol/service/CloudTasksService.java @@ -0,0 +1,168 @@ +package com.telecom.fn.batchcontrol.service; + +import com.google.cloud.tasks.v2.CloudTasksClient; +import com.google.cloud.tasks.v2.HttpMethod; +import com.google.cloud.tasks.v2.HttpRequest; +import com.google.cloud.tasks.v2.OidcToken; +import com.google.cloud.tasks.v2.QueueName; +import com.google.cloud.tasks.v2.Task; +import com.google.cloud.tasks.v2.CreateTaskRequest; +import com.google.protobuf.ByteString; +import com.telecom.fn.batchcontrol.model.BatchJobType; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class CloudTasksService { + + private static final String PROJECT_ID = System.getenv("GCP_PROJECT"); + private static final String REGION = System.getenv("GCP_REGION"); + private static final String QUEUE_NAME = "telecom-app-batch-queue"; + private static final String QUEUE_LOCATION = "asia-northeast3"; + private static final String CLOUD_RUN_JOB_NAME = "telecom-app-batch-core"; + + /** + * Cloud Task 생성 (즉시 실행용) + * Task가 Cloud Run Job :run 엔드포인트를 호출 + */ + public String createTask(BatchJobType jobType, String invMonth) throws Exception { + System.out.println("[CloudTasksService] ENTER createTask"); + System.out.println("[CloudTasksService] jobType=" + jobType + ", invMonth=" + invMonth); + + try (CloudTasksClient client = CloudTasksClient.create()) { + QueueName queueName = QueueName.of(PROJECT_ID, QUEUE_LOCATION, QUEUE_NAME); + System.out.println("[CloudTasksService] Queue=" + queueName.toString()); + + String cloudRunJobUrl = String.format( + "https://%s-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/%s/jobs/%s:run", + REGION, PROJECT_ID, CLOUD_RUN_JOB_NAME + ); + System.out.println("[CloudTasksService] CloudRunJobUrl=" + cloudRunJobUrl); + + String requestBody = buildCloudRunJobRequestBody(jobType, invMonth); + System.out.println("[CloudTasksService] RequestBody=" + requestBody); + + String serviceAccountEmail = getDefaultServiceAccount(); + + HttpRequest httpRequest = HttpRequest.newBuilder() + .setUrl(cloudRunJobUrl) + .setHttpMethod(HttpMethod.POST) + .putHeaders("Content-Type", "application/json") + .setBody(ByteString.copyFrom(requestBody, StandardCharsets.UTF_8)) + .setOidcToken(OidcToken.newBuilder() + .setServiceAccountEmail(serviceAccountEmail) + .setAudience(cloudRunJobUrl) + .build()) + .build(); + + Task task = Task.newBuilder() + .setHttpRequest(httpRequest) + .build(); + + CreateTaskRequest request = CreateTaskRequest.newBuilder() + .setParent(queueName.toString()) + .setTask(task) + .build(); + + Task createdTask = client.createTask(request); + System.out.println("[CloudTasksService] Task created: " + createdTask.getName()); + return createdTask.getName(); + } + } + + /** + * Cloud Run Job 실행 요청 본문 생성 + */ + private String buildCloudRunJobRequestBody(BatchJobType jobType, String invMonth) { + String jobArg = "\"--spring.batch.job.name=" + jobType.springBatchJobName() + "\""; + String monthArg = "\"--invMonth=" + invMonth + "\""; + String enabledArg = "\"--spring.batch.job.enabled=true\""; + String webTypeArg = "\"--spring.main.web-application-type=none\""; + + return "{\"overrides\": {\"containerOverrides\": [{\"args\": [" + + jobArg + ", " + monthArg + ", " + enabledArg + ", " + webTypeArg + + "]}]}}"; + } + + /** + * Cloud Tasks API URL 반환 (Scheduler 타겟용) + * https://cloudtasks.googleapis.com/v2/projects/{project}/locations/{location}/queues/{queue}/tasks + */ + public static String getCloudTasksApiUrl() { + return String.format( + "https://cloudtasks.googleapis.com/v2/projects/%s/locations/%s/queues/%s/tasks", + PROJECT_ID, QUEUE_LOCATION, QUEUE_NAME + ); + } + + /** + * Scheduler가 Cloud Tasks API에 POST할 요청 본문 생성 + * Task 생성 요청 (내부에 Cloud Run Job 호출 정보 포함) + */ + public static String buildSchedulerTaskRequestBody(BatchJobType jobType, String invMonth) { + String cloudRunJobUrl = String.format( + "https://%s-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/%s/jobs/%s:run", + REGION, PROJECT_ID, CLOUD_RUN_JOB_NAME + ); + + String serviceAccountEmail = getDefaultServiceAccountStatic(); + + String jobArg = "\"--spring.batch.job.name=" + jobType.springBatchJobName() + "\""; + String monthArg = (invMonth != null && !invMonth.isBlank()) + ? ", \"--invMonth=" + invMonth + "\"" : ""; + + String innerBody = "{\"overrides\": {\"containerOverrides\": [{\"args\": [" + + jobArg + monthArg + "]}]}}"; + String encodedBody = Base64.getEncoder().encodeToString( + innerBody.getBytes(StandardCharsets.UTF_8)); + + return String.format( + "{\"task\": {\"httpRequest\": {" + + "\"url\": \"%s\", " + + "\"httpMethod\": \"POST\", " + + "\"headers\": {\"Content-Type\": \"application/json\"}, " + + "\"body\": \"%s\", " + + "\"oidcToken\": {\"serviceAccountEmail\": \"%s\", \"audience\": \"%s\"}" + + "}}}", + cloudRunJobUrl, encodedBody, serviceAccountEmail, cloudRunJobUrl + ); + } + + /** + * Scheduler 요청 본문에서 invMonth 추출 + */ + public static String extractInvMonthFromSchedulerBody(String body) { + try { + int bodyStart = body.indexOf("\"body\":"); + if (bodyStart == -1) return null; + + int valueStart = body.indexOf("\"", bodyStart + 7) + 1; + int valueEnd = body.indexOf("\"", valueStart); + if (valueStart == 0 || valueEnd == -1) return null; + + String base64Body = body.substring(valueStart, valueEnd); + String decodedBody = new String(Base64.getDecoder().decode(base64Body), StandardCharsets.UTF_8); + + if (decodedBody.contains("--invMonth=")) { + int start = decodedBody.indexOf("--invMonth=") + 11; + int end = decodedBody.indexOf("\"", start); + if (end != -1) return decodedBody.substring(start, end); + } + } catch (Exception e) { + System.err.println("[CloudTasksService] Failed to extract invMonth: " + e.getMessage()); + } + return null; + } + + private String getDefaultServiceAccount() { + return getDefaultServiceAccountStatic(); + } + + private static String getDefaultServiceAccountStatic() { + String projectNumber = System.getenv("GCP_PROJECT_NUMBER"); + if (projectNumber == null || projectNumber.isBlank()) { + throw new IllegalStateException("Environment variable 'GCP_PROJECT_NUMBER' is required."); + } + return String.format("%s-compute@developer.gserviceaccount.com", projectNumber); + } +}