Skip to content

Commit

Permalink
feat(dx): move internal endpoints to v1
Browse files Browse the repository at this point in the history
  • Loading branch information
baktun14 committed Dec 17, 2024
1 parent d45c1e4 commit 31e5bd4
Show file tree
Hide file tree
Showing 14 changed files with 1,084 additions and 2 deletions.
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/gpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const route = createRoute({
method: "get",
path: "/gpu",
summary: "Get a list of gpu models and their availability.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/gpu instead.",
request: {
query: z.object({
provider: z.string().optional(),
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/gpuModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const route = createRoute({
path: "/gpu-models",
summary:
"Get a list of gpu models per vendor. Based on the content from https://raw.githubusercontent.com/akash-network/provider-configs/main/devices/pcie/gpus.json.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/gpu-models instead.",
responses: {
200: {
description: "List of gpu models per.",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/gpuPrices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const route = createRoute({
method: "get",
path: "/gpu-prices",
summary: "Get a list of gpu models with their availability and pricing.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/gpu-prices instead.",
responses: {
200: {
description: "List of gpu models with their availability and pricing.",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/leasesDuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const route = createRoute({
method: "get",
path: "/leases-duration/{owner}",
summary: "Get leases durations.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/leases-duration instead.",
request: {
params: z.object({
owner: z.string().openapi({ example: openApiExampleAddress })
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/providerDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const route = createRoute({
method: "get",
path: "/provider-dashboard/{owner}",
summary: "Get dashboard data for provider console.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/provider-dashboard instead.",
request: {
params: z.object({
owner: z.string().openapi({ example: openApiExampleProviderAddress })
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/internal/providerVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const route = createRoute({
method: "get",
path: "/provider-versions",
summary: "Get providers grouped by version.",
deprecated: true,
description: "This endpoint is deprecated. Please use the new endpoint /v1/provider-versions instead.",
responses: {
200: {
description: "List of providers grouped by version.",
Expand Down
162 changes: 162 additions & 0 deletions apps/api/src/routes/v1/gpu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { sub } from "date-fns";
import { QueryTypes } from "sequelize";
import { URL } from "url";

import { chainDb } from "@src/db/dbConnection";
import { toUTC } from "@src/utils";
import { isValidBech32Address } from "@src/utils/addresses";
import { env } from "@src/utils/env";

const route = createRoute({
method: "get",
path: "/gpu",
summary: "Get a list of gpu models and their availability.",
request: {
query: z.object({
provider: z.string().optional(),
vendor: z.string().optional(),
model: z.string().optional(),
memory_size: z.string().optional()
})
},
responses: {
200: {
description: "List of gpu models and their availability.",
content: {
"application/json": {
schema: z.object({
gpus: z.object({
total: z.object({
allocatable: z.number(),
allocated: z.number()
}),
details: z.record(
z.string(),
z.array(
z.object({
model: z.string(),
ram: z.string(),
interface: z.string(),
allocatable: z.number(),
allocated: z.number()
})
)
)
})
})
}
}
},
400: {
description: "Invalid provider parameter, should be a valid akash address or host uri"
}
}
});

export default new OpenAPIHono().openapi(route, async c => {
const provider = c.req.query("provider");
const vendor = c.req.query("vendor");
const model = c.req.query("model");
const memory_size = c.req.query("memory_size");

let provider_address: string | null = null;
let provider_hosturi: string | null = null;

if (provider) {
if (isValidBech32Address(provider)) {
provider_address = provider;
} else if (URL.canParse(provider)) {
provider_hosturi = provider;
} else {
return c.json({ error: "Invalid provider parameter, should be a valid akash address or host uri" }, 400);
}
}

const gpuNodes = await chainDb.query<{
hostUri: string;
name: string;
allocatable: number;
allocated: number;
modelId: string;
vendor: string;
modelName: string;
interface: string;
memorySize: string;
}>(
`
WITH snapshots AS (
SELECT DISTINCT ON("hostUri")
ps.id AS id,
"hostUri",
p."owner"
FROM provider p
INNER JOIN "providerSnapshot" ps ON ps.id=p."lastSuccessfulSnapshotId"
WHERE p."isOnline" IS TRUE OR ps."checkDate" >= :grace_date
)
SELECT DISTINCT ON (s."hostUri", n."name") s."hostUri", n."name", n."gpuAllocatable" AS allocatable, n."gpuAllocated" AS allocated, gpu."modelId", gpu.vendor, gpu.name AS "modelName", gpu.interface, gpu."memorySize"
FROM snapshots s
INNER JOIN "providerSnapshotNode" n ON n."snapshotId"=s.id AND n."gpuAllocatable" > 0
LEFT JOIN "providerSnapshotNodeGPU" gpu ON gpu."snapshotNodeId" = n.id
WHERE
(:vendor IS NULL OR gpu.vendor = :vendor)
AND (:model IS NULL OR gpu.name = :model)
AND (:memory_size IS NULL OR gpu."memorySize" = :memory_size)
AND (:provider_address IS NULL OR s."owner" = :provider_address)
AND (:provider_hosturi IS NULL OR s."hostUri" = :provider_hosturi)
`,
{
type: QueryTypes.SELECT,
replacements: {
vendor: vendor ?? null,
model: model ?? null,
memory_size: memory_size ?? null,
provider_address: provider_address ?? null,
provider_hosturi: provider_hosturi ?? null,
grace_date: toUTC(sub(new Date(), { minutes: env.PROVIDER_UPTIME_GRACE_PERIOD_MINUTES }))
}
}
);

const response = {
gpus: {
total: {
allocatable: gpuNodes.map(x => x.allocatable).reduce((acc, x) => acc + x, 0),
allocated: gpuNodes.map(x => x.allocated).reduce((acc, x) => acc + x, 0)
},
details: {} as { [key: string]: { model: string; ram: string; interface: string; allocatable: number; allocated: number }[] }
}
};

for (const gpuNode of gpuNodes) {
const vendorName = gpuNode.vendor ?? "<UNKNOWN>";
if (!(vendorName in response.gpus.details)) {
response.gpus.details[vendorName] = [];
}

const existing = response.gpus.details[vendorName].find(
x => x.model === gpuNode.modelName && x.interface === gpuNode.interface && x.ram === gpuNode.memorySize
);

if (existing) {
existing.allocatable += gpuNode.allocatable;
existing.allocated += gpuNode.allocated;
} else {
response.gpus.details[vendorName].push({
model: gpuNode.modelName,
ram: gpuNode.memorySize,
interface: gpuNode.interface,
allocatable: gpuNode.allocatable,
allocated: gpuNode.allocated
});
}
}

for (const vendorName in response.gpus.details) {
response.gpus.details[vendorName] = response.gpus.details[vendorName].sort(
(a, b) => a.model.localeCompare(b.model) || a.ram.localeCompare(b.ram) || a.interface.localeCompare(b.interface)
);
}

return c.json(response);
});
80 changes: 80 additions & 0 deletions apps/api/src/routes/v1/gpuModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import axios from "axios";

import { cacheKeys, cacheResponse } from "@src/caching/helpers";
import { GpuVendor, ProviderConfigGpusType } from "@src/types/gpu";
import { getGpuInterface } from "@src/utils/gpu";

const route = createRoute({
method: "get",
path: "/gpu-models",
summary:
"Get a list of gpu models per vendor. Based on the content from https://raw.githubusercontent.com/akash-network/provider-configs/main/devices/pcie/gpus.json.",
responses: {
200: {
description: "List of gpu models per.",
content: {
"application/json": {
schema: z.array(
z.object({
name: z.string(),
models: z.array(
z.object({
name: z.string(),
memory: z.array(z.string()),
interface: z.array(z.string())
})
)
})
)
}
}
}
}
});

export default new OpenAPIHono().openapi(route, async c => {
const response = await cacheResponse(60 * 2, cacheKeys.getGpuModels, async () => {
const res = await axios.get<ProviderConfigGpusType>("https://raw.githubusercontent.com/akash-network/provider-configs/main/devices/pcie/gpus.json");
return res.data;
});

const gpuModels: GpuVendor[] = [];

// Loop over vendors
for (const [, vendorValue] of Object.entries(response)) {
const vendor: GpuVendor = {
name: vendorValue.name,
models: []
};

// Loop over models
for (const [, modelValue] of Object.entries(vendorValue.devices)) {
const _modelValue = modelValue as {
name: string;
memory_size: string;
interface: string;
};
const existingModel = vendor.models.find(x => x.name === _modelValue.name);

if (existingModel) {
if (!existingModel.memory.includes(_modelValue.memory_size)) {
existingModel.memory.push(_modelValue.memory_size);
}
if (!existingModel.interface.includes(getGpuInterface(_modelValue.interface))) {
existingModel.interface.push(getGpuInterface(_modelValue.interface));
}
} else {
vendor.models.push({
name: _modelValue.name,
memory: [_modelValue.memory_size],
interface: [getGpuInterface(_modelValue.interface)]
});
}
}

gpuModels.push(vendor);
}

return c.json(gpuModels);
});
Loading

0 comments on commit 31e5bd4

Please sign in to comment.