Move the ingestion of compute to the platform
This commit is contained in:
@@ -1,24 +1,6 @@
|
||||
import { ActionFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { MachinePresetName } from "@trigger.dev/core/v3";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { validateJWTTokenAndRenew } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { workerQueue } from "~/services/worker.server";
|
||||
import { machinePresetFromName } from "~/v3/machinePresets.server";
|
||||
import { reportUsageEvent } from "~/v3/openMeter.server";
|
||||
|
||||
const JWTPayloadSchema = z.object({
|
||||
environment_id: z.string(),
|
||||
org_id: z.string(),
|
||||
project_id: z.string(),
|
||||
run_id: z.string(),
|
||||
machine_preset: z.string(),
|
||||
});
|
||||
|
||||
const BodySchema = z.object({
|
||||
durationMs: z.number(),
|
||||
});
|
||||
import { reportComputeUsage } from "~/services/platform.v3.server";
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
// Ensure this is a POST request
|
||||
@@ -26,74 +8,10 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
return { status: 405, body: "Method Not Allowed" };
|
||||
}
|
||||
|
||||
const jwtResult = await validateJWTTokenAndRenew(request, JWTPayloadSchema);
|
||||
|
||||
if (!jwtResult) {
|
||||
return { status: 401, body: "Unauthorized" };
|
||||
try {
|
||||
return await reportComputeUsage(request);
|
||||
} catch (e) {
|
||||
logger.error("Error reporting compute usage", { error: e });
|
||||
return new Response(null, { status: 500 });
|
||||
}
|
||||
|
||||
const rawJson = await request.json();
|
||||
|
||||
const json = BodySchema.safeParse(rawJson);
|
||||
|
||||
if (!json.success) {
|
||||
logger.error("Failed to parse request body", { rawJson });
|
||||
|
||||
return { status: 400, body: "Bad Request" };
|
||||
}
|
||||
|
||||
const preset = machinePresetFromName(jwtResult.payload.machine_preset as MachinePresetName);
|
||||
|
||||
logger.debug("[/api/v1/usage/ingest] Reporting usage", { jwtResult, json: json.data, preset });
|
||||
|
||||
if (json.data.durationMs > 0) {
|
||||
const costInCents = json.data.durationMs * preset.centsPerMs;
|
||||
|
||||
const taskRun = await prisma.taskRun.update({
|
||||
where: {
|
||||
id: jwtResult.payload.run_id,
|
||||
},
|
||||
data: {
|
||||
usageDurationMs: {
|
||||
increment: json.data.durationMs,
|
||||
},
|
||||
costInCents: {
|
||||
increment: json.data.durationMs * preset.centsPerMs,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await reportUsageEvent({
|
||||
source: "webapp",
|
||||
type: "usage",
|
||||
subject: jwtResult.payload.org_id,
|
||||
data: {
|
||||
durationMs: json.data.durationMs,
|
||||
costInCents: String(costInCents),
|
||||
taskIdentifier: taskRun.taskIdentifier,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error("Failed to report usage event, enqueing v3.reportUsage", { error: e });
|
||||
|
||||
await workerQueue.enqueue("v3.reportUsage", {
|
||||
orgId: jwtResult.payload.org_id,
|
||||
data: {
|
||||
costInCents: String(costInCents),
|
||||
},
|
||||
additionalData: {
|
||||
durationMs: json.data.durationMs,
|
||||
taskIdentifier: taskRun.taskIdentifier,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"x-trigger-jwt": jwtResult.jwt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,6 +181,15 @@ export async function getUsageSeries(organizationId: string, params: UsageSeries
|
||||
}
|
||||
}
|
||||
|
||||
export async function reportComputeUsage(request: Request) {
|
||||
const client = getClient();
|
||||
if (!client) return undefined;
|
||||
return fetch(`${process.env.BILLING_API_URL}/api/v1/usage/ingest/compute`, {
|
||||
method: "POST",
|
||||
headers: request.headers,
|
||||
body: await request.text(),
|
||||
});
|
||||
}
|
||||
export async function projectCreated(organization: Organization, project: Project) {
|
||||
if (project.version === "V2" || !isCloud()) {
|
||||
await createEnvironment(organization, project, "STAGING");
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { env } from "~/env.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
export type UsageEvent = {
|
||||
source: string;
|
||||
subject: string;
|
||||
type: string;
|
||||
id?: string;
|
||||
time?: Date;
|
||||
data?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export async function reportUsageEvent(event: UsageEvent) {
|
||||
if (!env.USAGE_OPEN_METER_BASE_URL || !env.USAGE_OPEN_METER_API_KEY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = {
|
||||
specversion: "1.0",
|
||||
id: event.id ?? randomUUID(),
|
||||
source: event.source,
|
||||
type: event.type,
|
||||
time: (event.time ?? new Date()).toISOString(),
|
||||
subject: event.subject,
|
||||
datacontenttype: "application/json",
|
||||
data: event.data,
|
||||
};
|
||||
|
||||
const url = `${env.USAGE_OPEN_METER_BASE_URL}/api/v1/events`;
|
||||
|
||||
logger.debug("Reporting usage event to OpenMeter", { url, body });
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"Content-Type": "application/cloudevents+json",
|
||||
Authorization: `Bearer ${env.USAGE_OPEN_METER_API_KEY}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error(`Failed to report usage event: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user