From d29e848f004706d0e07941d8d15e063ff8078b54 Mon Sep 17 00:00:00 2001 From: Mason <31372737+Ovaculos@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:35:36 -0500 Subject: [PATCH 1/3] vcpu size limit --- package-lock.json | 51 ++++++++++++++++++++++++ package.json | 1 + src/utils/AWS/EC2/getEC2InstanceTypes.ts | 36 +++++++++++++---- src/utils/AWS/EC2/getVCPULimit.ts | 31 ++++++++++++++ 4 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 src/utils/AWS/EC2/getVCPULimit.ts diff --git a/package-lock.json b/package-lock.json index c08d6db..4dad18b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@aws-sdk/client-dynamodb": "^3.772.0", "@aws-sdk/client-ec2": "^3.767.0", "@aws-sdk/client-iam": "^3.758.0", + "@aws-sdk/client-service-quotas": "^3.792.0", "@aws-sdk/client-ssm": "^3.774.0", "@aws-sdk/lib-dynamodb": "^3.772.0", "argon2": "^0.41.1", @@ -366,6 +367,56 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/client-service-quotas": { + "version": "3.792.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-service-quotas/-/client-service-quotas-3.792.0.tgz", + "integrity": "sha512-uT1sNlbHFcka6AwS8wDk5NGVeypDC8xl/qg51pFI7jVdWCuoAvT/3cG3z9fAB1deYiL6IKHas+mP53+k5GgElA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.775.0", + "@aws-sdk/credential-provider-node": "3.787.0", + "@aws-sdk/middleware-host-header": "3.775.0", + "@aws-sdk/middleware-logger": "3.775.0", + "@aws-sdk/middleware-recursion-detection": "3.775.0", + "@aws-sdk/middleware-user-agent": "3.787.0", + "@aws-sdk/region-config-resolver": "3.775.0", + "@aws-sdk/types": "3.775.0", + "@aws-sdk/util-endpoints": "3.787.0", + "@aws-sdk/util-user-agent-browser": "3.775.0", + "@aws-sdk/util-user-agent-node": "3.787.0", + "@smithy/config-resolver": "^4.1.0", + "@smithy/core": "^3.2.0", + "@smithy/fetch-http-handler": "^5.0.2", + "@smithy/hash-node": "^4.0.2", + "@smithy/invalid-dependency": "^4.0.2", + "@smithy/middleware-content-length": "^4.0.2", + "@smithy/middleware-endpoint": "^4.1.0", + "@smithy/middleware-retry": "^4.1.0", + "@smithy/middleware-serde": "^4.0.3", + "@smithy/middleware-stack": "^4.0.2", + "@smithy/node-config-provider": "^4.0.2", + "@smithy/node-http-handler": "^4.0.4", + "@smithy/protocol-http": "^5.1.0", + "@smithy/smithy-client": "^4.2.0", + "@smithy/types": "^4.2.0", + "@smithy/url-parser": "^4.0.2", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.8", + "@smithy/util-defaults-mode-node": "^4.0.8", + "@smithy/util-endpoints": "^3.0.2", + "@smithy/util-middleware": "^4.0.2", + "@smithy/util-retry": "^4.0.2", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/client-ssm": { "version": "3.787.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.787.0.tgz", diff --git a/package.json b/package.json index 364a940..7fc66e7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@aws-sdk/client-dynamodb": "^3.772.0", "@aws-sdk/client-ec2": "^3.767.0", "@aws-sdk/client-iam": "^3.758.0", + "@aws-sdk/client-service-quotas": "^3.792.0", "@aws-sdk/client-ssm": "^3.774.0", "@aws-sdk/lib-dynamodb": "^3.772.0", "argon2": "^0.41.1", diff --git a/src/utils/AWS/EC2/getEC2InstanceTypes.ts b/src/utils/AWS/EC2/getEC2InstanceTypes.ts index be4ae50..b645e3a 100644 --- a/src/utils/AWS/EC2/getEC2InstanceTypes.ts +++ b/src/utils/AWS/EC2/getEC2InstanceTypes.ts @@ -1,8 +1,9 @@ -import { - EC2Client, - DescribeInstanceTypesCommand, - DescribeInstanceTypesCommandOutput +import { + EC2Client, + DescribeInstanceTypesCommand, + DescribeInstanceTypesCommandOutput, } from "@aws-sdk/client-ec2"; +import { getVCPULimit } from "./getVCPULimit"; const ec2Client = new EC2Client({ region: process.env.REGION }); @@ -16,17 +17,36 @@ export async function getEC2InstanceTypes(): Promise> { Filters: [ { Name: "instance-type", - Values: ["m8g.*", "m7g.*", "c8g.*", "c7gn.*", "r8g.*", "t2.micro", "t2.small"], + Values: [ + "m8g.*", + "m7g.*", + "c8g.*", + "c7gn.*", + "r8g.*", + "t2.micro", + "t2.small", + ], }, ], NextToken: nextToken, }); - const response: DescribeInstanceTypesCommandOutput = await ec2Client.send(command); + const response: DescribeInstanceTypesCommandOutput = + await ec2Client.send(command); if (response.InstanceTypes) { + const region = process.env.REGION || "us-east-1"; + const vCPULimit = (await getVCPULimit(region)) || 32; + allSpecifiedInstanceTypes.push( - ...response.InstanceTypes.map((type) => type.InstanceType ?? "") + ...response.InstanceTypes.filter((type) => { + return ( + type.VCpuInfo?.DefaultVCpus && + type.VCpuInfo?.DefaultVCpus <= vCPULimit + ); + }).map((type) => { + return type.InstanceType ?? ""; + }), ); } @@ -47,4 +67,4 @@ export async function getEC2InstanceTypes(): Promise> { console.error("Error fetching instance types:", error); return {}; } -} \ No newline at end of file +} diff --git a/src/utils/AWS/EC2/getVCPULimit.ts b/src/utils/AWS/EC2/getVCPULimit.ts new file mode 100644 index 0000000..c3be8a2 --- /dev/null +++ b/src/utils/AWS/EC2/getVCPULimit.ts @@ -0,0 +1,31 @@ +import { + ServiceQuotasClient, + GetServiceQuotaCommand, +} from "@aws-sdk/client-service-quotas"; + +const serviceCode = "ec2"; +const quotaCode = "L-1216C47A"; + +const params = { + ServiceCode: serviceCode, + QuotaCode: quotaCode, +}; + +const command = new GetServiceQuotaCommand(params); + +export async function getVCPULimit(region: string) { + const client = new ServiceQuotasClient({ region }); + + try { + const data = await client.send(command); + + if (data.Quota && data.Quota.Value !== undefined) { + const VCPULimit = data.Quota.Value; + return VCPULimit; + } else { + console.log("Could not retrieve the quota value from the response."); + } + } catch (error) { + console.error("Error getting service quota:", error); + } +} From cd84474fad577df2575f0a4f9a1930ab1d22ceca Mon Sep 17 00:00:00 2001 From: Mason <31372737+Ovaculos@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:57:48 -0500 Subject: [PATCH 2/3] CAn't do wrong instance types --- src/app/api/instanceTypes/route.ts | 16 +++-- .../hardware/components/InstanceTypePage.tsx | 42 +++++++---- src/utils/AWS/EC2/getEC2InstanceTypes.ts | 72 +++++++++++-------- 3 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/app/api/instanceTypes/route.ts b/src/app/api/instanceTypes/route.ts index 32eb87e..d6e6c89 100644 --- a/src/app/api/instanceTypes/route.ts +++ b/src/app/api/instanceTypes/route.ts @@ -1,12 +1,18 @@ -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { getEC2InstanceTypes } from "@/utils/AWS/EC2/getEC2InstanceTypes"; -export async function GET() { +export async function GET(request: NextRequest) { try { - const instanceTypes = await getEC2InstanceTypes(); + const searchParams = request.nextUrl.searchParams; + const architecture = + (searchParams.get("architecture") as "all" | "amd64" | "arm64") || "all"; + const instanceTypes = await getEC2InstanceTypes(architecture); return NextResponse.json({ instanceTypes }); } catch (error) { console.error("API Error:", error); - return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); + return NextResponse.json( + { message: "Internal Server Error" }, + { status: 500 }, + ); } -} \ No newline at end of file +} diff --git a/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx b/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx index 478f882..338d9b1 100644 --- a/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx +++ b/src/app/instances/[name]/hardware/components/InstanceTypePage.tsx @@ -1,6 +1,8 @@ "use client"; import { useInstanceContext } from "../../InstanceContext"; +import { useNotificationsContext } from "@/app/NotificationContext"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import axios from "axios"; import ErrorBanner from "@/app/instances/components/ErrorBanner"; @@ -10,8 +12,9 @@ type InstanceTypes = Record; export function InstanceTypePage() { const [instanceTypes, setInstanceTypes] = useState({}); + const { addNotification, formPending } = useNotificationsContext(); const { instance } = useInstanceContext(); - const [saving, setSaving] = useState(false); + const router = useRouter(); const [isLoading, setIsLoading] = useState(true); const [selectedInstanceType, setSelectedInstanceType] = useState(""); const [filteredInstanceTypes, setFilteredInstanceTypes] = useState( @@ -23,7 +26,10 @@ export function InstanceTypePage() { useEffect(() => { const fetchInstanceTypes = async () => { try { - const { data } = await axios.get("/api/instanceTypes"); + const architecture = instance?.type.includes("t2") ? "amd64" : "arm64"; + const { data } = await axios.get( + `/api/instanceTypes?architecture=${architecture}`, + ); setIsLoading(false); setInstanceTypes(data.instanceTypes); } catch (error) { @@ -32,7 +38,7 @@ export function InstanceTypePage() { }; fetchInstanceTypes(); - }, []); + }, [instance?.type]); useEffect(() => { setFilteredInstanceTypes(instanceTypes[selectedInstanceType] ?? []); @@ -62,20 +68,32 @@ export function InstanceTypePage() { const updateHardware = async () => { const validationErrors = validateInstanceTypeAndSize(); if (validationErrors.length > 0) { - setSaving(false); + return false; + } + + if (!instance?.name) { return false; } try { + await addNotification({ + type: "instanceType", + status: "pending", + instanceName: instance?.name, + path: "instances", + message: `Updating type of ${instance?.name} to ${instanceSize}`, + }); + await axios.put(`/api/instances/${instance?.name}/hardware/type`, { instanceId: instance?.id, instanceType: instanceSize, region: instance?.region, + instanceName: instance?.name, }); return true; } catch (error) { + console.log(error); if (!error) return; - setSaving(false); setErrors([ "Failed to update instance hardware. You must be upgrading the size.", ]); @@ -147,7 +165,7 @@ export function InstanceTypePage() { ) : ( -
+