-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dx): move internal endpoints to v1
- Loading branch information
Showing
14 changed files
with
1,084 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
Oops, something went wrong.